認証
LINE Blockchain Developers APIは、有効なリクエストなのかどうかを確認するために認証を行います。認証のためのAPI keyを発行する方法は、APIの紹介を参照してください。
サーバーの時間を確認するAPIを除いたすべてのAPIリクエストは、HTTPヘッダーに認証情報を送る必要があります。 サーバーは、与えられた認証情報に基づいて以下のようにAPIリクエストの有効性を判断します。無効なAPIリクエストは処理しません。
- リクエストの
timestamp
とサーバーの現在時刻(Unix Epoch time)が±5分以上の差が出るとエラーを返します。 - 1つの
service-api-key
において11分内に同じnonce
を再利用することはできません。成功したリクエストのnonce
を11分内に再利用すると、エラーを返します。 - 1つの
signature
は一度のみ有効です。
以下は、リクエストヘッダーに含まれるべき認証情報です。
Header | Description |
---|---|
timestamp | リクエストが作成された時刻で、UTC基準ミリ秒単位のUnix Epoch時間で表示されます。この値とサーバーとの時間差は5分を超えてはなりません。 |
nonce | アルファベットの大文字・小文字と数字からなる8桁の任意の文字列です。成功したリクエストのnonce は、11分内に再利用できません。 |
service-api-key | LINE Blockchain Developersコンソールで発行されたサービスのAPI keyです。LINE Blockchain Developersで正常に有効化された値であるべきです。 |
signature | APIリクエストをLINE Blockchain Developersコンソールで発行されたサービスのAPI secretで署名した結果です。受信したサーバーで生成した署名と同じであるべきです。署名を生成するを参照してください。 |
API keyは、LINE Blockchain Developersコンソールのサービスの設定ページで確認できます。
署名の生成
署名は以下のように生成できます。
- ナンス(Nonce)、タイムスタンプ、HTTPメソッド、リクエストパス、クエリ文字列、リクエストボディの文字列を順番に付けた文字列を生成します。
- 発行されたAPI secretを利用して1の結果をHMAC-SHA512で署名します。
- 2の結果をBase64でエンコードします。
署名に使用される情報は以下のとおりです。
- ナンスとタイムスタンプは、HTTPヘッダーに送信する
nonce
、timestamp
と同じです。 - HTTPメソッドは、APIリクエストのHTTPメソッドを大文字で表記した文字列です。
- リクエストパスは、API endpointの
api-path
です。
例えば、リクエストAPI endpointが、https://test-api.blockchain.line.me/v1/wallets/link000000000000000000000000000000000000000?page=2&msgType=MsgSend
の場合、リクエストパスは/v1/wallets/link000000000000000000000000000000000000000
です。 - クエリ文字列は、API endpointのクエリパラメータです。
例えば、リクエストAPI endpointが
https://test-api.blockchain.line.me/v1/wallets/link000000000000000000000000000000000000000?page=2&msgType=MsgSend
の場合、クエリ文字列はpage=2&msg=MsgSend
です。 - リクエストボディの文字列は、HTTPリクエストのボディに含まれているkeyとvalueをクエリ文字列のように'key=value&'でつなげたものです。すべてのkeyをアルファベットの昇順に並べ替えてからその順番につなげる必要があります。
- リクエストボディに配列が入っている場合は、配列keyの下位要素のkeyをピリオド(.)でつなげ、全体のkeyとして使用します。また、配列の各要素で該当するvalueをコンマ(,)でつなげたものを全体のvalueとして使用します。例4を参照してください。
- リクエストボディに配列が入っており、一部のkeyが配列内でオプションである場合、指定されていないvalueを空の文字列で処理する必要があります。例4を参照してください。
JavaScriptのサンプル
以下のコードは、JavaScriptで署名を生成する方法を示します。
ES6で書いたコードです。以前のバージョンをサポートするにはBabelを使用してください。
- signature-generator.js
- request-body-flattener.js
- .babelrc
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);
}
}
import _ from "lodash";
const EMPTY = "";
export default class RequestBodyFlattener {
static flatten(requestBody = {}) {
const objBody = _.cloneDeep(requestBody)
const flatPair = {}
Object.keys(objBody).forEach(key => {
const value = objBody[key]
if(Array.isArray(value)) {
let allSubKeys = []
value.forEach(elem => {
allSubKeys = _.union(allSubKeys, Object.keys(elem))
})
value.forEach(elem => {
allSubKeys.forEach(subKey => {
const flatKey = `${key}.${subKey}`
const flatRawValue = elem[subKey] ? elem[subKey] : EMPTY
const prevFlatValue = flatPair[flatKey]
flatPair[flatKey] =
_.isUndefined(prevFlatValue) ? flatRawValue : `${prevFlatValue},${flatRawValue}`
})
})
} else {
flatPair[key] = objBody[key]
}
})
return Object.keys(flatPair).sort().map(key => `${key}=${flatPair[key]}`).join('&');
}
}
{
"presets": ["@babel/preset-env"]
}
Pythonのサンプル
以下のコードは、Pythonで署名を生成する方法を示します。
- signature_generator.py
- request_flattener.py
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
class RequestBodyFlattener:
def __flatten_key_value(self, key, value):
if (isinstance(value, str)):
return f"{key}={value}"
if (isinstance(value, list)):
l_key_value = {}
for index, ele in enumerate(value):
for lkey in list(ele.keys() | l_key_value.keys()):
if lkey in ele.keys():
lvalue = ele[lkey]
else:
lvalue = ""
if (lkey in l_key_value.keys()):
l_key_value[lkey] = f"{l_key_value[lkey]},{lvalue}"
else:
l_key_value[lkey] = f"{',' * index}{lvalue}"
return "&".join("%s=%s" % (f"{key}.{lkey}", lvalue) for (lkey, lvalue) in sorted(l_key_value.items()))
def flatten(self, body: dict = {}):
sorted_body = sorted(body.items())
return "&".join(self.__flatten_key_value(key, value) for (key, value) in sorted_body)
Kotlinのサンプル
以下のコードは、Kotlinで署名を生成する方法を示します。
- SignatureGenerator.kt
- QueryParameterFlattener.kt
- RequestBodyFlattener.kt
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"
}
}
import java.util.TreeMap
interface QueryParameterOrderer {
fun sort(queryParams: Map<String, List<String?>>): Map<String, List<String?>>
fun order(queryParams: Map<String, List<String?>>): Map<String, List<String?>> {
return sort(queryParams)
}
}
interface QueryParameterFlattener {
fun flatten(queryParams: Map<String, List<String?>>): String
}
class DefaultOrderedQueryParameterFlattener: QueryParameterFlattener, QueryParameterOrderer {
override fun flatten(queryParams: Map<String, List<String?>>): String {
val orderedMap = order(queryParams)
return if (orderedMap.isEmpty()) {
""
} else {
orderedMap.filterValues { it.isNotEmpty() }.map { (k, v) ->
"$k=${v.joinToString(",")}"
}.joinToString("&")
}
}
override fun sort(queryParams: Map<String, List<String?>>): Map<String, List<String?>> {
return TreeMap<String, List<String?>>(queryParams)
}
}
import org.apache.commons.lang3.StringUtils
import java.util.TreeMap
interface RequestBodyFlattener {
fun flatten(body: Map<String, Any?>): String
}
class DefaultRequestBodyFlattener: RequestBodyFlattener {
override fun flatten(body: Map<String, Any?>): String {
val bodyTreeMap = TreeMap<String, Any?>()
bodyTreeMap.putAll(body)
@Suppress("UNCHECKED_CAST")
return if (bodyTreeMap.isEmpty()) {
""
} else {
bodyTreeMap.filterValues { it != null }.map { (k, v) ->
when (v) {
is String -> "$k=$v"
is List<*> -> {
val listTreeMap = TreeMap<String, String?>()
v as List<Map<String, String>>
v.forEachIndexed { index, map ->
map.keys.union(listTreeMap.keys).forEach { key ->
val value = map[key] ?: StringUtils.EMPTY
if (listTreeMap[key] == null) {
listTreeMap[key] = "${",".repeat(index)}$value"
} else {
listTreeMap[key] = "${listTreeMap[key]},$value"
}
}
}
listTreeMap.map { (lk, kv) ->
"$k.$lk=$kv"
}.joinToString("&")
}
else -> throw IllegalArgumentException()
}
}.joinToString("&")
}
}
}
Goのサンプル
以下のコードは、Go-langで署名を生成する方法を示します。
- signature_generator.go
- request_body_flattener.go
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
}
import (
"fmt"
"sort"
"strings"
)
func _createFlatPair(flatPair map[string]string, body map[string]interface{}) {
for key, value := range(body) {
is_primitive := false;
expr := "";
switch value.(type) {
case []interface{}:
allSubKeys := make(map[string]bool, 0)
for _, subv:= range value.([]interface{}) {
submap := subv.(map[string]interface{})
for k, _ := range(submap) {
allSubKeys[k] = true;
}
}
for _, subv:= range value.([]interface{}) {
submap := subv.(map[string]interface{})
for subKey, _ := range(allSubKeys) {
flatKey := fmt.Sprintf("%s.%s", key, subKey)
flatRawValue := "";
if x, found := submap[subKey]; found {
flatRawValue = x.(string)
}
if prevFlatValue, found := flatPair[flatKey]; found {
flatPair[flatKey] = fmt.Sprintf("%s,%s", prevFlatValue, flatRawValue)
} else {
flatPair[flatKey] = flatRawValue
}
}
}
// handle primitive types
default:
expr = fmt.Sprint(value)
is_primitive = true;
}
if is_primitive {
flatPair[key] = expr;
}
}
}
func Flatten(body map[string]interface{}) string {
flatPair := make(map[string]string) // we're going to convert objBody to flatPair
_createFlatPair(flatPair, body)
keys := make([]string, 0, len(flatPair))
flattenBody := make([]string, 0, len(flatPair))
for k := range flatPair {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
flattenBody = append(flattenBody, fmt.Sprintf("%s=%s", k, flatPair[k]))
}
ret := strings.Join(flattenBody, "&")
return ret;
}
PHPのサンプル
以下のコードは、PHPで署名を生成する方法を示します。
- signature_generator.php
- request_body_flattener.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;
}
}
?>
<?php
class RequestBodyFlattener {
public function flatten($body) {
$objBody = array_merge(array(), $body);
$flatPair = []; // we're going to convert objBody to flatPair
foreach ($objBody as $key => $value) {
if (is_int($value))
$value = strval($value);
if (is_array($value)) {
$allSubKeys = [];
foreach ($value as $elem) {
$allSubKeys += array_keys($elem);
}
foreach ($value as $elem) {
foreach ($allSubKeys as $subKey) {
$flatKey = "{$key}.{$subKey}";
$flatRawValue = $elem[$subKey] ? $elem[$subKey] : "";
$prevFlatValue = $flatPair[$flatKey];
$flatPair[$flatKey] =
is_null($prevFlatValue) ? $flatRawValue : "{$prevFlatValue},{$flatRawValue}";
}
}
} else {
$flatPair[$key] = $value;
}
}
ksort($flatPair);
$flattenBody = [];
foreach ($flatPair as $key => $value) {
array_push($flattenBody, "{$key}={$value}");
}
return join("&", $flattenBody);
}
}
?>
署名の例
署名を生成し、それを利用してAPIリクエストを送る方法をいくつかの例で説明します。 例で共通して使用される情報は、以下のとおりです。
- API Key: 136db0ad-0fe1-456f-96a4-329be3f93036
- API Secret: 9256bf8a-2b86-42fe-b3e0-d3079d0141fe
- Timestamp: 1581850266351
- Nonce: Bp0IqgXE
例1.リクエストパスのみの場合
最初の例は、リクエストパスのみのAPIリクエストです。
- HTTP method: GET
- Request path: /v1/wallets
署名に必要な文字列は以下のように生成できます。
Bp0IqgXE1581850266351GET/v1/wallets
echo
とOpenSSLを使用してHMAC SHA512署名を生成し、Base64でエンコードします。
echo -n "Bp0IqgXE1581850266351GET/v1/wallets" | openssl dgst -sha512 -binary -hmac "9256bf8a-2b86-42fe-b3e0-d3079d0141fe" | base64
> 2LtyRNI16y/5/RdoTB65sfLkO0OSJ4pCuz2+ar0npkRbk1/dqq1fbt1FZo7fueQl1umKWWlBGu/53KD2cptcCA==
curl
を利用して生成した署名をAPIリクエストヘッダーに送信します。
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=='
例2.リクエストパスとクエリパラメータがある場合
例2では、リクエストパスとクエリパラメータがあるAPIリクエストです。
- 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
署名に必要な文字列は以下のように生成できます。リクエストパスとクエリ文字列の間に「?」が含まれていることを覚えておいてください。クエリパラメータは順番の変更なしにそのまま入力します。
Bp0IqgXE1581850266351GET/v1/wallets/tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq/transactions?page=2&msgType=coin/MsgSend
echo
とOpenSSLを使用してHMAC SHA512署名を生成し、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==
curl
を利用して生成した署名をAPIリクエストヘッダーに送信します。
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=='
例3.リクエストパスとリクエストボディ(配列は除く)がある場合
例3では、リクエストパスとリクエストボディがあるAPIリクエストです。
- HTTP method: PUT
- Request path: /v1/item-tokens/61e14383/non-fungibles/10000001/00000001
- Request body string:
json { "ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq", "ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=", "name": "NewName" }
署名に必要な文字列は以下のように生成できます。
Bp0IqgXE1581850266351PUT/v1/item-tokens/61e14383/non-fungibles/10000001/00000001?name=NewName&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=
上記のように、クエリ文字列がなくてもリクエストパスとリクエストボディの間に「?」が含まれます。また、リクエストボディは、keyをアルファベットの昇順に並べ替えてから追加する必要があります。
使用中のプログラミング言語が提供する並べ替え関数を使用すると、簡単に並べ替えることができます。
echo
とOpenSSLを使用してHMAC SHA512署名を生成し、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==
curl
を利用して生成した署名をAPIリクエストヘッダーに送信します。
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"
}'
例4.リクエストパスとリクエストボディ(配列を含む)がある場合
最後の例は、リクエストパスと配列を含むリクエストボディがあるAPIリクエストです。
- HTTP method: POST
- Request path: /v1/item-tokens/61e14383/non-fungibles/multi-mint
- Request body:
json { "ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq", "ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=", "toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp", "mintList": [ { "tokenType": "10000001", "name": "NewNFT" }, { "tokenType": "10000003", "name": "NewNFT2", "meta": "New nft 2 meta information" } ] }
署名に必要な文字列は以下のように生成できます。
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
上記のように、クエリパラメータがなくてもリクエストパスとリクエストボディの間に「?」が含まれます。また、リクエストボディは、keyをアルファベットの昇順に並べ替えてから追加する必要があります。
使用中のプログラミング言語が提供する並べ替え関数を使用すると、簡単に並べ替えることができます。
上記の例で、mintList
の最初の要素には選択フィールドであるmeta
が存在しません。この場合は、その値を空の文字列に代わり、"mintList.meta=,New nft 2 meta information"のように作ります。もし、2番目の要素にもmeta
がない場合は、"mintList.meta"自体を除きます。
echo
とOpenSSLを使用してHMAC SHA512署名を生成し、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==
を利用して生成した署名をAPIリクエストヘッダーに送信します。
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"
}
]
}'
もし、以下のようにmintList
にmeta
を使用していないリクエストボディの場合はどうなるでしょうか?
{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2"
}
]
}
この場合は、以下のように署名する文字列からmintList.meta
keyを除きます。
Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp
今度は、mintList
の選択フィールドであるmeta
にnullを入力した状況を考えてみましょう。
{
"ownerAddress": "tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq",
"ownerSecret": "uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=",
"toAddress": "tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp",
"mintList": [
{
"tokenType": "10000001",
"name": "NewNFT"
},
{
"tokenType": "10000003",
"name": "NewNFT2",
"meta": null
}
]
}
この場合は、以下のようにnull値を除いた文字列を署名して使用します。
Bp0IqgXE1581850266351POST/v1/item-tokens/61e14383/non-fungibles/multi-mint?mintList.name=NewNFT,NewNFT2&mintList.tokenType=10000001,10000003&ownerAddress=tlink1fr9mpexk5yq3hu6jc0npajfsa0x7tl427fuveq&ownerSecret=uhbdnNvIqQFnnIFDDG8EuVxtqkwsLtDR/owKInQIYmo=&toAddress=tlink18zxqds28mmg8mwduk32csx5xt6urw93ycf8jwp