请求签名
路印API涉及到两种不同类别的签名。一种是通用API请求签名,用来验证API调用被用户授权;另一种是路印协议链下请求签名,用来向路印协议证明链下请求被用户授权。我们分别对这两种类别做个说明。
通用API请求签名
签名生成算法
- 初始化空字符串
signatureBase
; - 将API请求的HTTP方法字符串追加到
signatureBase
; - 将“&”字符附加到
signatureBase
; - 将百分号编码后(percent-encoded)后的完整URL路径(不包括“?”和查询参数)追加到
signatureBase
; - 将“&”字符附加到
signatureBase
; - 初始化空字符串
parameterString
; - 对于GET / DELETE 请求:
- 将请求里的参数按键的字典顺序升序排序,得到排过序后的键/值对;
- 将百分号编码后后的键附加到
parameterString
; - 将“=”字符附加到
parameterString
; - 将百分号编码后后的值附加到
parameterString
; - 如果有更多的键/值对,请在
parameterString
后面附加“&”字符,并重复上述操作;
- 对于POST / PUT 请求;
- 将发送请求的Body JSON字符串附加到
parameterString
;
- 将发送请求的Body JSON字符串附加到
- 将百分号编码后后的
parameterString
附加到signatureBase
; - 计算
signatureBase
的SHA-256哈希值hash
; - 对
hash
用账号的私钥privateKey
做签名,得到三个值:Rx
,Ry
, 和S
; - 将
Rx
,Ry
, 和S
通过逗号分隔拼接成最终签名字符串:${Rx},${Ry},${S}
。
HTTP Method and URL
请使用大写的HTTP方法:
- GET
- POST
- PUT
- DELETE
URL中请一定包含HTTPS协议头,确保协议头和接入URL全部小写,比如:
https://api3.loopring.io/api/v2/apiKey
示例
假设上面的URL包含下列Query参数:
https://api3.loopring.io/api/v2/apiKey?publicKeyX=13375450901292179417154974849571793069
911517354720397125027633242680470075859&publicKeyY=133754509012921794171549748495717930
69911517354720397125027633242680470075859&accountId=1
即:
参数名 | 参数值 |
---|---|
publicKeyX | 13375450901292179417154974849571793069911517354720397125027633242680470075859 |
publicKeyY | 13375450901292179417154974849571793069911517354720397125027633242680470075859 |
accountId | 1 |
那么,parameterString
应该为:
accountId=1&publicKeyX=1337545090129217941715497484957179306991151735472039712502763324
2680470075859&publicKeyY=13375450901292179417154974849571793069911517354720397125027633
242680470075859
signatureBase
应该为:
GET&https%3A%2F%2Fapi3.loopring.io%2Fapi%2Fv2%2FapiKey&accountId%3D1%26publicKeyX%3D1337
5450901292179417154974849571793069911517354720397125027633242680470075859%26publicKeyY%
3D13375450901292179417154974849571793069911517354720397125027633242680470075859
路印协议链下请求签名
路印协议3.1.1支持“订单”,和“链下提现”两种链下请求。由于这两种链下请求都会造成对交易所默克尔树的修改,通过路印API提交这是两种数据时,必须附带路印协议要求的特殊的签名。
路印协议3.1.1还支持“取消订单”链下请求,但会在后续的3.5版本中将其去掉。因此路印中继不会支持该链下请求。
链下请求签名包括以下步骤:
- 对请求
r
(JSON类型)进行规整,生成一个字符串s
。 - 计算
s
的Poseidon哈希h
(见下面章节)。 - 对
h
用账号的私钥privateKey
做签名,得到三个值:Rx
,Ry
, 和S
(见下面章节)。 - 将
h
、Rx
、Ry
、 和S
转换成字符串后合并到r
当中(请注意名字的改变)。
{
...,
"hash": ...,
"signatureRx": "16367919966553849834214288740952929086694704883595501207054796240908626703398",
"signatureRy": "5706650945525714138019517276433581394702490352313697178959212750249847059862",
"signatureS": "410675649229327911665390972834008845981102813589085982164606483611508480748"
}
订单签名
订单中一些数据项需要按照特定序列化成一个整数数组,对这个数组计算Poseidon哈希,然后对该哈希做EdDSA签名。
订单的序列化规则,哈希,签名方式必须严格遵循路印协议规范。
下面我们用Python代码做示范:
def sign_int_array(privateKey, serialized, t):
PoseidonHashParams = poseidon_params(
SNARK_SCALAR_FIELD,
t,
6,
53,
b'poseidon',
5,
security_target=128
)
hash = poseidon(serialized, PoseidonHashParams)
signedMessage = PoseidonEdDSA.sign(hash, FQ(int(privateKey)))
return ({
"hash": str(hash),
"signatureRx": str(signedMessage.sig.R.x),
"signatureRy": str(signedMessage.sig.R.y),
"signatureS": str(signedMessage.sig.s),
})
def serialize_order(order):
return [
int(order["exchangeId"]),
int(order["orderId"]),
int(order["accountId"]),
int(order["tokenSId"]),
int(order["tokenBId"]),
int(order["amountS"]),
int(order["amountB"]),
int(order["allOrNone"]=="true"),
int(order["validSince"]),
int(order["validUntil"]),
int(order["maxFeeBips"]),
int(order["buy"]=="true"),
int(order["label"])
]
def sign_order(privateKey, order):
serialized = serialize_order(order)
signed = sign_int_array(serialized, 14 /* 注意这个t值 */)
order.update(signed)
如果您不使用ethsnarks代码仓库计算Poseidon哈希,请一定注意Poseidon参数的配置,保证其与路印协议使用的参数完全一致。否则验证签名会失败。
链下提现签名
目前的路印API还不支持客户端提交链下提现请求。不过我们会很快增加这个API。
下面是链下提现的一个例子:
{
"exchangeId": 2,
"accountId":100,
"tokenId": 0,
"amount": 1000000000000000000,
"feeTokenId": "2",
"amountFee": 20000000000000000000,
"label": 0,
"nonce": 10
}
其中的nonce
值必须从0开始,不间断增加。
用Python对其签名的代码如下:
def serialize_offchain_withdrawal(withdrawal):
return [
int(withdrawal['exchangeId']),
int(withdrawal['accountId']),
int(withdrawal['tokenId']),
int(withdrawal['amount']),
int(withdrawal['feeTokenId']),
int(withdrawal['amountFee']),
int(withdrawal['label']),
int(withdrawal['nonce'])
]
def sign_offchain_withdrawal(privateKey, offchainWithdrawal):
serialized = serialize_offchain_withdrawal(offchainWithdrawal)
signed = sign_int_array(serialized, 9 /* 注意这个t值 */)
offchainWithdrawal.update(signed)
内部转账签名
内部转账请求中一些数据项需要按照特定序列化成一个整数数组,对这个数组计算Poseidon哈希,然后对该哈希做EdDSA签名。
下面是内部转账的一个例子:
{
"exchangeId": 2,
"sender":100,
"receiver":101,
"tokenId": 0,
"amount": 1000000000000000000,
"feeTokenId": 2,
"amountFee": 20000000000000000000,
"label": 0,
"nonce": 10
}
其中的nonce
值必须从0开始,不间断增加。
用Python对其签名的代码如下:
def serialize_internal_transfer(transfer):
return [
int(transfer['exchangeId']),
int(transfer['sender']),
int(transfer['receiver']),
int(transfer['tokenId']),
int(transfer['amount']),
int(transfer['feeTokenId']),
int(transfer['amountFee']),
int(transfer['label']),
int(transfer['nonce'])
]
def sign_internal_transfer(privateKey, transfer):
serialized = serialize_internal_transfer(transfer)
signed = sign_int_array(serialized, 10 /* 注意这个t值 */)
transfer.update(signed)
除了EDDSA的签名,用户还需要使用ECDSA对内部转账请求进行签名。内部转账请求的一些数据需要构造成一个Json的字符串,然后采用sha256哈希算法,计算得到hash,转成16进制字符串形式,加上固定的头:"Sign this message to authorize Loopring Pay: ",对组合之后的字符采用personal _sign 方法签名。
使用Js对其签名的代码如下:
function serialize_transfer(transfer) {
const data = {
exchangeId: transfer.exchangeId,
sender: transfer.sender,
receiver: transfer.receiver,
token: transfer.tokenId,
amount: transfer.amount,
tokenF: transfer.feeTokenId,
amountF: transfer.amountFee,
label: transfer.label,
nonce: transfer.nonce,
memo:transfer.memo || ""
};
return "0x" + sha256(JSON.stringify(data)).toString('hex');
}
function sign_internal_transfer(transfer){
const transferData = serialize_transfer(transfer);
const prefix = "Sign this message to authorize Loopring Pay: ";
const message = prefix + transferData;
const sig = personal_sign(privateKey, message);
}
参考资料
您可以通过下列文献和代码仓库了解更多关于Poseidon哈希和EdDSA签名的细节。
- ethsnarks:https://github.com/HarryR/ethsnarks.git
- SHA256 Hash:https://en.wikipedia.org/wiki/SHA-2
- EdDSA:https://en.wikipedia.org/wiki/EdDSA
- Poseidon Hash:https://www.poseidon-hash.info/
您也可以参考我们的示范代码了解更多应用细节。