请求签名

路印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
  • 百分号编码后后的parameterString附加到signatureBase
  • 计算signatureBaseSHA-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版本中将其去掉。因此路印中继不会支持该链下请求。

链下请求签名包括以下步骤:

  1. 对请求r(JSON类型)进行规整,生成一个字符串s
  2. 计算sPoseidon哈希h(见下面章节)。
  3. h用账号的私钥privateKey做签名,得到三个值:Rx,Ry, 和S(见下面章节)。
  4. hRxRy、 和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签名的细节。

  1. ethsnarkshttps://github.com/HarryR/ethsnarks.git
  2. SHA256 Hashhttps://en.wikipedia.org/wiki/SHA-2
  3. EdDSAhttps://en.wikipedia.org/wiki/EdDSA
  4. Poseidon Hashhttps://www.poseidon-hash.info/

您也可以参考我们的示范代码了解更多应用细节。

results matching ""

    No results matching ""