mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
785 lines
26 KiB
Markdown
785 lines
26 KiB
Markdown
2 Signature
|
|
How To Sign Message
|
|
How To GET Your L2 Private Key
|
|
To sign messages on Layer 2, you need to obtain your L2 private key. This key is used to generate signatures that authorize various actions on the platform.
|
|
|
|
|
|
How To GET Your L2 Private Key
|
|
Warning: Keep your private key secure and never share it with anyone. Anyone with access to your private key can sign messages on your behalf.
|
|
|
|
Signature Algorithm
|
|
The signature algorithm used is Ecdsa (Elliptic Curve Digital Signature Algorithm). This algorithm ensures that signatures are secure and verifiable.
|
|
|
|
L2Signature for Operations (e.g., Order, Transfer, Withdraw): This will use Pedersen hash for signing. However, this hash computation will consume significantly more CPU resources.
|
|
|
|
<!-- logo -->
|
|
<h1 align='center'>StarkWare Crypto Utils</h1>
|
|
|
|
<!-- tag line -->
|
|
<h4 align='center'> Signatures, keys and Pedersen hash on STARK friendly elliptic curve</h4>
|
|
|
|
<!-- primary badges -->
|
|
<p align="center">
|
|
<a href="https://www.w3schools.com/js/">
|
|
<img src='https://badges.aleen42.com/src/javascript.svg' />
|
|
</a>
|
|
<a href="https://www.npmjs.com/package/@starkware-industries/starkware-crypto-utils">
|
|
<img src='https://img.shields.io/npm/v/@starkware-industries/starkware-crypto-utils?label=npm' />
|
|
</a>
|
|
<a href="https://starkware.co/">
|
|
<img src="https://img.shields.io/badge/powered_by-StarkWare-navy">
|
|
</a>
|
|
</p>
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
// using npm
|
|
npm i @starkware-industries/starkware-crypto-utils
|
|
|
|
// using yarn
|
|
yarn add @starkware-industries/starkware-crypto-utils
|
|
```
|
|
|
|
## How to use it
|
|
|
|
```js
|
|
const starkwareCrypto = require('@starkware-industries/starkware-crypto-utils');
|
|
```
|
|
|
|
## API
|
|
|
|
```javascript
|
|
|
|
{
|
|
prime,
|
|
ec: starkEc,
|
|
constantPoints,
|
|
shiftPoint,
|
|
maxEcdsaVal, // Data.
|
|
pedersen,
|
|
getLimitOrderMsgHash,
|
|
getTransferMsgHash,
|
|
sign,
|
|
verify,
|
|
assertInRange,
|
|
getTransferMsgHashWithFee,
|
|
getLimitOrderMsgHashWithFee // Function.
|
|
|
|
asset: {
|
|
getAssetType,
|
|
getAssetId // Function.
|
|
},
|
|
|
|
keyDerivation: {
|
|
StarkExEc: ec.n, // Data.
|
|
getPrivateKeyFromEthSignature,
|
|
privateToStarkKey,
|
|
getKeyPairFromPath,
|
|
getAccountPath,
|
|
grindKey // Function.
|
|
},
|
|
|
|
messageUtils: {
|
|
assertInRange // Function.
|
|
}
|
|
}
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Signing a StarkEx order
|
|
|
|
```javascript
|
|
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
|
|
const testData = require('test/config/signature_test_data.json');
|
|
|
|
const privateKey = testData.meta_data.party_a_order.private_key.substring(2);
|
|
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
|
|
const publicKey = starkwareCrypto.ec.keyFromPublic(
|
|
keyPair.getPublic(true, 'hex'),
|
|
'hex'
|
|
);
|
|
const publicKeyX = publicKey.pub.getX();
|
|
|
|
assert(
|
|
publicKeyX.toString(16) ===
|
|
testData.settlement.party_a_order.public_key.substring(2),
|
|
`Got: ${publicKeyX.toString(16)}.
|
|
Expected: ${testData.settlement.party_a_order.public_key.substring(2)}`
|
|
);
|
|
|
|
const {party_a_order: partyAOrder} = testData.settlement;
|
|
const msgHash = starkwareCrypto.getLimitOrderMsgHash(
|
|
partyAOrder.vault_id_sell, // - vault_sell (uint31)
|
|
partyAOrder.vault_id_buy, // - vault_buy (uint31)
|
|
partyAOrder.amount_sell, // - amount_sell (uint63 decimal str)
|
|
partyAOrder.amount_buy, // - amount_buy (uint63 decimal str)
|
|
partyAOrder.token_sell, // - token_sell (hex str with 0x prefix < prime)
|
|
partyAOrder.token_buy, // - token_buy (hex str with 0x prefix < prime)
|
|
partyAOrder.nonce, // - nonce (uint31)
|
|
partyAOrder.expiration_timestamp // - expiration_timestamp (uint22)
|
|
);
|
|
|
|
assert(
|
|
msgHash === testData.meta_data.party_a_order.message_hash.substring(2),
|
|
`Got: ${msgHash}. Expected: ` +
|
|
testData.meta_data.party_a_order.message_hash.substring(2)
|
|
);
|
|
|
|
const msgSignature = starkwareCrypto.sign(keyPair, msgHash);
|
|
const {r, s} = msgSignature;
|
|
|
|
assert(starkwareCrypto.verify(publicKey, msgHash, msgSignature));
|
|
assert(
|
|
r.toString(16) === partyAOrder.signature.r.substring(2),
|
|
`Got: ${r.toString(16)}. Expected: ${partyAOrder.signature.r.substring(2)}`
|
|
);
|
|
assert(
|
|
s.toString(16) === partyAOrder.signature.s.substring(2),
|
|
`Got: ${s.toString(16)}. Expected: ${partyAOrder.signature.s.substring(2)}`
|
|
);
|
|
|
|
// The following is the JSON representation of an order:
|
|
console.log('Order JSON representation: ');
|
|
console.log(partyAOrder);
|
|
console.log('\n');
|
|
```
|
|
|
|
### StarkEx key serialization
|
|
|
|
```javascript
|
|
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
|
|
|
|
const pubXStr = publicKey.pub.getX().toString('hex');
|
|
const pubYStr = publicKey.pub.getY().toString('hex');
|
|
|
|
// Verify Deserialization.
|
|
const pubKeyDeserialized = starkwareCrypto.ec.keyFromPublic(
|
|
{x: pubXStr, y: pubYStr},
|
|
'hex'
|
|
);
|
|
assert(starkwareCrypto.verify(pubKeyDeserialized, msgHash, msgSignature));
|
|
```
|
|
|
|
### Signing a StarkEx order with fee
|
|
|
|
```javascript
|
|
const privateKey = testData.meta_data.party_a_order.private_key.substring(2);
|
|
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
|
|
const publicKey = starkwareCrypto.ec.keyFromPublic(
|
|
keyPair.getPublic(true, 'hex'),
|
|
'hex'
|
|
);
|
|
const publicKeyX = publicKey.pub.getX();
|
|
|
|
assert(
|
|
publicKeyX.toString(16) ===
|
|
testData.settlement.party_a_order.public_key.substring(2),
|
|
`Got: ${publicKeyX.toString(16)}.
|
|
Expected: ${testData.settlement.party_a_order.public_key.substring(2)}`
|
|
);
|
|
|
|
const {party_a_order: partyAOrder} = testData.settlement;
|
|
const feeInfo = testData.fee_info_user;
|
|
const msgHash = starkwareCrypto.getLimitOrderMsgHashWithFee(
|
|
partyAOrder.vault_id_sell, // - vault_sell (uint64)
|
|
partyAOrder.vault_id_buy, // - vault_buy (uint64)
|
|
partyAOrder.amount_sell, // - amount_sell (uint63 decimal str)
|
|
partyAOrder.amount_buy, // - amount_buy (uint63 decimal str)
|
|
partyAOrder.token_sell, // - token_sell (hex str with 0x prefix < prime)
|
|
partyAOrder.token_buy, // - token_buy (hex str with 0x prefix < prime)
|
|
partyAOrder.nonce, // - nonce (uint31)
|
|
partyAOrder.expiration_timestamp, // - expiration_timestamp (uint22)
|
|
feeInfo.token_id, // - token (hex str with 0x prefix < prime)
|
|
feeInfo.source_vault_id, // - fee_source_vault_id (uint31)
|
|
feeInfo.fee_limit // - amount (uint63 decimal str)
|
|
);
|
|
|
|
assert(
|
|
msgHash ===
|
|
testData.meta_data.party_a_order_with_fee.message_hash.substring(2),
|
|
`Got: ${msgHash}. Expected: ` +
|
|
testData.meta_data.party_a_order_with_fee.message_hash.substring(2)
|
|
);
|
|
|
|
// The following is the JSON representation of an order:
|
|
console.log('Order With Fee JSON representation: ');
|
|
// Fee info is added to the order, and will be also be seen in the JSON of Settlement.
|
|
partyAOrder.fee_info = feeInfo; // eslint-disable-line
|
|
console.log(partyAOrder);
|
|
console.log('\n');
|
|
```
|
|
|
|
### StarkEx transfer
|
|
|
|
```javascript
|
|
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
|
|
const testData = require('test/config/signature_test_data.json');
|
|
|
|
const privateKey = testData.meta_data.transfer_order.private_key.substring(2);
|
|
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
|
|
const publicKey = starkwareCrypto.ec.keyFromPublic(
|
|
keyPair.getPublic(true, 'hex'),
|
|
'hex'
|
|
);
|
|
const publicKeyX = publicKey.pub.getX();
|
|
|
|
assert(
|
|
publicKeyX.toString(16) === testData.transfer_order.public_key.substring(2),
|
|
`Got: ${publicKeyX.toString(16)}.
|
|
Expected: ${testData.transfer_order.public_key.substring(2)}`
|
|
);
|
|
|
|
const transfer = testData.transfer_order;
|
|
const msgHash = starkwareCrypto.getTransferMsgHash(
|
|
transfer.amount, // - amount (uint63 decimal str)
|
|
transfer.nonce, // - nonce (uint31)
|
|
transfer.sender_vault_id, // - sender_vault_id (uint31)
|
|
transfer.token, // - token (hex str with 0x prefix < prime)
|
|
transfer.target_vault_id, // - target_vault_id (uint31)
|
|
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
|
|
transfer.expiration_timestamp // - expiration_timestamp (uint22)
|
|
);
|
|
|
|
assert(
|
|
msgHash === testData.meta_data.transfer_order.message_hash.substring(2),
|
|
`Got: ${msgHash}. Expected: ` +
|
|
testData.meta_data.transfer_order.message_hash.substring(2)
|
|
);
|
|
|
|
// The following is the JSON representation of a transfer:
|
|
console.log('Transfer JSON representation: ');
|
|
console.log(transfer);
|
|
console.log('\n');
|
|
```
|
|
|
|
### StarkEx conditional transfer
|
|
|
|
```javascript
|
|
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
|
|
const testData = require('test/config/signature_test_data.json');
|
|
|
|
const privateKey =
|
|
testData.meta_data.conditional_transfer_order.private_key.substring(2);
|
|
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
|
|
const publicKey = starkwareCrypto.ec.keyFromPublic(
|
|
keyPair.getPublic(true, 'hex'),
|
|
'hex'
|
|
);
|
|
const publicKeyX = publicKey.pub.getX();
|
|
|
|
assert(
|
|
publicKeyX.toString(16) ===
|
|
testData.conditional_transfer_order.public_key.substring(2),
|
|
`Got: ${publicKeyX.toString(16)}.
|
|
Expected: ${testData.conditional_transfer_order.public_key.substring(
|
|
2
|
|
)}`
|
|
);
|
|
|
|
const transfer = testData.conditional_transfer_order;
|
|
const msgHash = starkwareCrypto.getTransferMsgHash(
|
|
transfer.amount, // - amount (uint63 decimal str)
|
|
transfer.nonce, // - nonce (uint31)
|
|
transfer.sender_vault_id, // - sender_vault_id (uint31)
|
|
transfer.token, // - token (hex str with 0x prefix < prime)
|
|
transfer.target_vault_id, // - target_vault_id (uint31)
|
|
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
|
|
transfer.expiration_timestamp, // - expiration_timestamp (uint22)
|
|
transfer.condition // - condition (hex str with 0x prefix < prime)
|
|
);
|
|
|
|
assert(
|
|
msgHash ===
|
|
testData.meta_data.conditional_transfer_order.message_hash.substring(2),
|
|
`Got: ${msgHash}. Expected: ` +
|
|
testData.meta_data.conditional_transfer_order.message_hash.substring(2)
|
|
);
|
|
|
|
// The following is the JSON representation of a transfer:
|
|
console.log('Conditional Transfer JSON representation: ');
|
|
console.log(transfer);
|
|
console.log('\n');
|
|
```
|
|
|
|
### StarkEx transfer with fee
|
|
|
|
```javascript
|
|
const privateKey = testData.meta_data.transfer_order.private_key.substring(2);
|
|
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
|
|
const publicKey = starkwareCrypto.ec.keyFromPublic(
|
|
keyPair.getPublic(true, 'hex'),
|
|
'hex'
|
|
);
|
|
const publicKeyX = publicKey.pub.getX();
|
|
|
|
assert(
|
|
publicKeyX.toString(16) === testData.transfer_order.public_key.substring(2),
|
|
`Got: ${publicKeyX.toString(16)}.
|
|
Expected: ${testData.transfer_order.public_key.substring(2)}`
|
|
);
|
|
|
|
const transfer = testData.transfer_order;
|
|
const feeInfo = testData.fee_info_user;
|
|
const msgHash = starkwareCrypto.getTransferMsgHashWithFee(
|
|
transfer.amount, // - amount (uint63 decimal str)
|
|
transfer.nonce, // - nonce (uint31)
|
|
transfer.sender_vault_id, // - sender_vault_id (uint64)
|
|
transfer.token, // - token (hex str with 0x prefix < prime)
|
|
transfer.target_vault_id, // - target_vault_id (uint64)
|
|
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
|
|
transfer.expiration_timestamp, // - expiration_timestamp (uint22)
|
|
feeInfo.token_id, // - token (hex str with 0x prefix < prime)
|
|
feeInfo.source_vault_id, // - fee_source_vault_id (uint64)
|
|
feeInfo.fee_limit // - amount (uint63 decimal str)
|
|
);
|
|
|
|
assert(
|
|
msgHash ===
|
|
testData.meta_data.transfer_order_with_fee.message_hash.substring(2),
|
|
`Got: ${msgHash}. Expected: ` +
|
|
testData.meta_data.transfer_order.message_hash.substring(2)
|
|
);
|
|
|
|
// The following is the JSON representation of a transfer:
|
|
console.log('Transfer With Fee JSON representation: ');
|
|
console.log(transfer);
|
|
console.log('\n');
|
|
```
|
|
|
|
### StarkEx conditional Transfer with fee
|
|
|
|
```javascript
|
|
const privateKey =
|
|
testData.meta_data.conditional_transfer_order.private_key.substring(2);
|
|
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
|
|
const publicKey = starkwareCrypto.ec.keyFromPublic(
|
|
keyPair.getPublic(true, 'hex'),
|
|
'hex'
|
|
);
|
|
const publicKeyX = publicKey.pub.getX();
|
|
|
|
assert(
|
|
publicKeyX.toString(16) ===
|
|
testData.conditional_transfer_order.public_key.substring(2),
|
|
`Got: ${publicKeyX.toString(16)}.
|
|
Expected: ${testData.conditional_transfer_order.public_key.substring(
|
|
2
|
|
)}`
|
|
);
|
|
|
|
const transfer = testData.conditional_transfer_order;
|
|
const feeInfo = testData.fee_info_user;
|
|
const msgHash = starkwareCrypto.getTransferMsgHashWithFee(
|
|
transfer.amount, // - amount (uint63 decimal str)
|
|
transfer.nonce, // - nonce (uint31)
|
|
transfer.sender_vault_id, // - sender_vault_id (uint64)
|
|
transfer.token, // - token (hex str with 0x prefix < prime)
|
|
transfer.target_vault_id, // - target_vault_id (uint64)
|
|
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
|
|
transfer.expiration_timestamp, // - expiration_timestamp (uint22)
|
|
feeInfo.token_id, // - token (hex str with 0x prefix < prime)
|
|
feeInfo.source_vault_id, // - fee_source_vault_id (uint64)
|
|
feeInfo.fee_limit, // - amount (uint63 decimal str)
|
|
transfer.condition // - condition (hex str with 0x prefix < prime)
|
|
);
|
|
|
|
assert(
|
|
msgHash ===
|
|
testData.meta_data.conditional_transfer_order_with_fee.message_hash.substring(
|
|
2
|
|
),
|
|
`Got: ${msgHash}. Expected: ` +
|
|
testData.meta_data.conditional_transfer_order.message_hash.substring(2)
|
|
);
|
|
|
|
// The following is the JSON representation of a transfer:
|
|
console.log('Conditional Transfer With Fee JSON representation: ');
|
|
console.log(transfer);
|
|
console.log('\n');
|
|
```
|
|
|
|
### Adding a matching order to create a settlement
|
|
|
|
```javascript
|
|
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
|
|
const testData = require('test/config/signature_test_data.json');
|
|
|
|
const privateKey = testData.meta_data.party_b_order.private_key.substring(2);
|
|
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
|
|
const publicKey = starkwareCrypto.ec.keyFromPublic(
|
|
keyPair.getPublic(true, 'hex'),
|
|
'hex'
|
|
);
|
|
const publicKeyX = publicKey.pub.getX();
|
|
|
|
assert(
|
|
publicKeyX.toString(16) ===
|
|
testData.settlement.party_b_order.public_key.substring(2),
|
|
`Got: ${publicKeyX.toString(16)}.
|
|
Expected: ${testData.settlement.party_b_order.public_key.substring(2)}`
|
|
);
|
|
|
|
const {party_b_order: partyBOrder} = testData.settlement;
|
|
const msgHash = starkwareCrypto.getLimitOrderMsgHash(
|
|
partyBOrder.vault_id_sell, // - vault_sell (uint31)
|
|
partyBOrder.vault_id_buy, // - vault_buy (uint31)
|
|
partyBOrder.amount_sell, // - amount_sell (uint63 decimal str)
|
|
partyBOrder.amount_buy, // - amount_buy (uint63 decimal str)
|
|
partyBOrder.token_sell, // - token_sell (hex str with 0x prefix < prime)
|
|
partyBOrder.token_buy, // - token_buy (hex str with 0x prefix < prime)
|
|
partyBOrder.nonce, // - nonce (uint31)
|
|
partyBOrder.expiration_timestamp // - expiration_timestamp (uint22)
|
|
);
|
|
|
|
assert(
|
|
msgHash === testData.meta_data.party_b_order.message_hash.substring(2),
|
|
`Got: ${msgHash}. Expected: ` +
|
|
testData.meta_data.party_b_order.message_hash.substring(2)
|
|
);
|
|
|
|
const msgSignature = starkwareCrypto.sign(keyPair, msgHash);
|
|
const {r, s} = msgSignature;
|
|
|
|
assert(starkwareCrypto.verify(publicKey, msgHash, msgSignature));
|
|
assert(
|
|
r.toString(16) === partyBOrder.signature.r.substring(2),
|
|
`Got: ${r.toString(16)}. Expected: ${partyBOrder.signature.r.substring(2)}`
|
|
);
|
|
assert(
|
|
s.toString(16) === partyBOrder.signature.s.substring(2),
|
|
`Got: ${s.toString(16)}. Expected: ${partyBOrder.signature.s.substring(2)}`
|
|
);
|
|
|
|
// The following is the JSON representation of a settlement:
|
|
console.log('Settlement JSON representation: ');
|
|
console.log(testData.settlement);
|
|
```
|
|
|
|
## Valid transfer with sender_vault_id=2\*\*63+10
|
|
|
|
```javascript
|
|
const transfer = testData.transfer_order_2nd_valid_range;
|
|
const feeInfo = testData.fee_info_user;
|
|
|
|
const msgHash = starkwareCrypto.getTransferMsgHashWithFee(
|
|
transfer.amount, // - amount (uint63 decimal str)
|
|
transfer.nonce, // - nonce (uint31)
|
|
transfer.sender_vault_id, // - sender_vault_id (uint64)
|
|
transfer.token, // - token (hex str with 0x prefix < prime)
|
|
transfer.target_vault_id, // - target_vault_id (uint64)
|
|
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
|
|
transfer.expiration_timestamp, // - expiration_timestamp (uint22)
|
|
feeInfo.token_id, // - token (hex str with 0x prefix < prime)
|
|
feeInfo.source_vault_id, // - fee_source_vault_id (uint64)
|
|
feeInfo.fee_limit, // - amount (uint63 decimal str)
|
|
transfer.condition // - condition (hex str with 0x prefix < prime)
|
|
);
|
|
|
|
assert(
|
|
msgHash ===
|
|
testData.meta_data.transfer_order_2nd_valid_range.message_hash.substring(2),
|
|
`Got: ${msgHash}. Expected: ` +
|
|
testData.meta_data.transfer_order_2nd_valid_range.message_hash.substring(2)
|
|
);
|
|
|
|
// The following is the JSON representation of a transfer with sender_vault_id in the second
|
|
// valid range:
|
|
console.log('Transfer JSON representation: ');
|
|
console.log(transfer);
|
|
console.log('\n');
|
|
```
|
|
|
|
## License
|
|
|
|
[Apache License 2.0](LICENSE.md)
|
|
|
|
Java L2Signature Demo
|
|
Below is a Java implementation of the Ecdsa signature algorithm. This example demonstrates how to sign a message using a private key.
|
|
|
|
Copy
|
|
|
|
public static CreateOrderRequest signOrder(
|
|
CreateOrderRequest request,
|
|
Contract contract,
|
|
Coin quotelCoin,
|
|
PrivateKey privateKey) {
|
|
BigInteger msgHash = L2SignUtil.hashLimitOrder(
|
|
request.getSide() == OrderSide.BUY,
|
|
BigIntUtil.toBigInt(quotelCoin.getStarkExAssetId()),
|
|
BigIntUtil.toBigInt(contract.getStarkExSyntheticAssetId()),
|
|
BigIntUtil.toBigInt(quotelCoin.getStarkExAssetId()),
|
|
UnsignedLong.valueOf(new BigDecimal(request.getL2Value())
|
|
.multiply(new BigDecimal(BigIntUtil.toBigInt(quotelCoin.getStarkExResolution())))
|
|
.toBigIntegerExact()),
|
|
UnsignedLong.valueOf(new BigDecimal(request.getL2Size())
|
|
.multiply(new BigDecimal(BigIntUtil.toBigInt(contract.getStarkExResolution())))
|
|
.toBigIntegerExact()),
|
|
UnsignedLong.valueOf(new BigDecimal(request.getL2LimitFee())
|
|
.multiply(new BigDecimal(BigIntUtil.toBigInt(quotelCoin.getStarkExResolution())))
|
|
.toBigIntegerExact()),
|
|
UnsignedLong.fromLongBits(request.getAccountId()),
|
|
UnsignedInteger.valueOf(request.getL2Nonce()),
|
|
UnsignedInteger.valueOf(request.getL2ExpireTime() / (60 * 60 * 1000L)));
|
|
Signature signature = Ecdsa.sign(msgHash, privateKey);
|
|
return request.toBuilder()
|
|
.setL2Signature(L2Signature.newBuilder()
|
|
.setR(BigIntUtil.toHexStr(signature.r))
|
|
.setS(BigIntUtil.toHexStr(signature.s))
|
|
.build())
|
|
.build();
|
|
}
|
|
|
|
public static BigInteger hashLimitOrder(
|
|
boolean isBuyingSynthetic,
|
|
BigInteger assetIdCollateral,
|
|
BigInteger assetIdSynthetic,
|
|
BigInteger assetIdFee,
|
|
UnsignedLong amountCollateral,
|
|
UnsignedLong amountSynthetic,
|
|
UnsignedLong maxAmountFee,
|
|
UnsignedLong positionId,
|
|
UnsignedInteger nonce,
|
|
UnsignedInteger expirationTimestamp) {
|
|
BigInteger assetIdSell;
|
|
BigInteger assetIdBuy;
|
|
UnsignedLong amountSell;
|
|
UnsignedLong amountBuy;
|
|
if (isBuyingSynthetic) {
|
|
assetIdSell = assetIdCollateral;
|
|
assetIdBuy = assetIdSynthetic;
|
|
amountSell = amountCollateral;
|
|
amountBuy = amountSynthetic;
|
|
} else {
|
|
assetIdSell = assetIdSynthetic;
|
|
assetIdBuy = assetIdCollateral;
|
|
amountSell = amountSynthetic;
|
|
amountBuy = amountCollateral;
|
|
}
|
|
BigInteger packedMessage0 = amountSell.bigIntegerValue();
|
|
packedMessage0 = packedMessage0.shiftLeft(64).add(amountBuy.bigIntegerValue());
|
|
packedMessage0 = packedMessage0.shiftLeft(64).add(maxAmountFee.bigIntegerValue());
|
|
packedMessage0 = packedMessage0.shiftLeft(32).add(nonce.bigIntegerValue());
|
|
|
|
BigInteger packedMessage1 = BigInteger.valueOf(3);
|
|
packedMessage1 = packedMessage1.shiftLeft(64).add(positionId.bigIntegerValue());
|
|
packedMessage1 = packedMessage1.shiftLeft(64).add(positionId.bigIntegerValue());
|
|
packedMessage1 = packedMessage1.shiftLeft(64).add(positionId.bigIntegerValue());
|
|
packedMessage1 = packedMessage1.shiftLeft(32).add(expirationTimestamp.bigIntegerValue());
|
|
packedMessage1 = packedMessage1.shiftLeft(17);
|
|
|
|
BigInteger msg = pedersenHash(assetIdSell, assetIdBuy);
|
|
msg = pedersenHash(msg, assetIdFee);
|
|
msg = pedersenHash(msg, packedMessage0);
|
|
msg = pedersenHash(msg, packedMessage1);
|
|
return msg;
|
|
}
|
|
|
|
public static BigInteger pedersenHash(BigInteger... input) {
|
|
BigInteger[][] points = PEDERSEN_POINTS;
|
|
Point shiftPoint = new Point(points[0][0], points[0][1]);
|
|
for (int i = 0; i < input.length; i++) {
|
|
BigInteger x = input[i];
|
|
for (int j = 0; j < 252; j++) {
|
|
int pos = 2 + i * 252 + j;
|
|
Point pt = new Point(points[pos][0], points[pos][1]);
|
|
if (x.and(BigInteger.ONE).intValue() != 0) {
|
|
shiftPoint = EcMath.add(shiftPoint, pt, Curve.secp256k1.A, Curve.secp256k1.P);
|
|
}
|
|
x = x.shiftRight(1);
|
|
}
|
|
}
|
|
return shiftPoint.x;
|
|
}
|
|
|
|
public static Signature sign(BigInteger msgHash, PrivateKey privateKey) {
|
|
Curve curve = privateKey.curve;
|
|
BigInteger randNum = new BigInteger(curve.N.toByteArray().length * 8 - 1, new SecureRandom()).abs().add(BigInteger.ONE);
|
|
Point randomSignPoint = EcMath.multiply(curve.G, randNum, curve.N, curve.A, curve.P);
|
|
BigInteger r = randomSignPoint.x.mod(curve.N);
|
|
BigInteger s = ((msgHash.add(r.multiply(privateKey.secret))).multiply(EcMath.inv(randNum, curve.N))).mod(curve.N);
|
|
return Signature.create(r, s);
|
|
}
|
|
Signature Construction Guide
|
|
This section provides detailed instructions on constructing signatures for various actions on the platform.
|
|
|
|
Withdrawal Signature
|
|
Used to authorize withdrawing assets from Layer 2 to an Ethereum address.
|
|
|
|
Parameters
|
|
assetIdCollateral - Asset ID for the collateral token from meta_data.coinList.starkExAssetId
|
|
|
|
positionId - User's account ID in Layer 2
|
|
|
|
ethAddress - Destination Ethereum address for withdrawal
|
|
|
|
nonce - Unique transaction identifier to prevent replay attacks
|
|
|
|
expirationTimestamp - Unix timestamp when signature expires
|
|
|
|
amount - Amount to withdraw in base units
|
|
|
|
Calculation
|
|
The following TypeScript function constructs the withdrawal message for signing:
|
|
|
|
Copy
|
|
// Construct withdrawal message for signing
|
|
function getWithdrawalToAddressMsg({
|
|
assetIdCollateral,
|
|
positionId,
|
|
ethAddress,
|
|
nonce,
|
|
expirationTimestamp,
|
|
amount
|
|
}) {
|
|
// Pack parameters into 256-bit words
|
|
const w1 = assetIdCollateral;
|
|
let w5 = BigInt(withdrawalToAddress); // Constant identifier
|
|
w5 = (w5 << 64) + BigInt(positionId);
|
|
w5 = (w5 << 32) + BigInt(nonce);
|
|
w5 = (w5 << 64) + BigInt(amount);
|
|
w5 = (w5 << 32) + BigInt(expirationTimestamp);
|
|
w5 = w5 << 49;
|
|
|
|
// Calculate Pedersen hash
|
|
return pedersen([
|
|
pedersen([w1, ethAddress]),
|
|
w5.toString(16)
|
|
]);
|
|
}
|
|
Limit Order Signature
|
|
Used to authorize a limit order for perpetual trading.
|
|
|
|
Parameters
|
|
assetIdSynthetic - Synthetic asset ID from meta_data.contractList.starkExSyntheticAssetId
|
|
|
|
assetIdCollateral - Collateral asset ID from meta_data.coinList.starkExAssetId
|
|
|
|
isBuyingSynthetic - true for buy orders, false for sell orders
|
|
|
|
assetIdFee - Fee token asset ID from meta_data.coinList.starkExAssetId
|
|
|
|
amountSynthetic - Amount of synthetic asset
|
|
|
|
amountCollateral - Amount of collateral asset
|
|
|
|
maxAmountFee - Maximum fee amount allowed
|
|
|
|
nonce - Unique order identifier
|
|
|
|
positionId - User's position ID
|
|
|
|
expirationTimestamp - Unix timestamp when order expires
|
|
|
|
Calculation
|
|
The following TypeScript function constructs the limit order message for signing:
|
|
|
|
Copy
|
|
function getLimitOrderMsg({
|
|
assetIdSynthetic,
|
|
assetIdCollateral,
|
|
isBuyingSynthetic,
|
|
assetIdFee,
|
|
amountSynthetic,
|
|
amountCollateral,
|
|
maxAmountFee,
|
|
nonce,
|
|
positionId,
|
|
expirationTimestamp
|
|
}) {
|
|
// Determine sell/buy assets based on order side
|
|
const [assetIdSell, assetIdBuy] = isBuyingSynthetic
|
|
? [assetIdCollateral, assetIdSynthetic]
|
|
: [assetIdSynthetic, assetIdCollateral];
|
|
const [amountSell, amountBuy] = isBuyingSynthetic
|
|
? [amountCollateral, amountSynthetic]
|
|
: [amountSynthetic, amountCollateral];
|
|
|
|
// Pack order data into 256-bit words
|
|
const w1 = assetIdSell;
|
|
const w2 = assetIdBuy;
|
|
const w3 = assetIdFee;
|
|
|
|
// Calculate message hash
|
|
let msg = pedersen([w1, w2]);
|
|
msg = pedersen([msg, w3]);
|
|
|
|
let w4 = BigInt(amountSell);
|
|
w4 = (w4 << 64) + BigInt(amountBuy);
|
|
w4 = (w4 << 64) + BigInt(maxAmountFee);
|
|
w4 = (w4 << 32) + BigInt(nonce);
|
|
msg = pedersen([msg, w4.toString(16)]);
|
|
|
|
let w5 = BigInt(limitOrderWithFees); // Constant identifier
|
|
w5 = (w5 << 64) + BigInt(positionId);
|
|
w5 = (w5 << 64) + BigInt(positionId);
|
|
w5 = (w5 << 64) + BigInt(positionId);
|
|
w5 = (w5 << 32) + BigInt(expirationTimestamp);
|
|
w5 = w5 << 17;
|
|
|
|
return pedersen([msg, w5.toString(16)]);
|
|
}
|
|
Transfer Signature
|
|
Used to authorize transfers between Layer 2 accounts.
|
|
|
|
Parameters
|
|
assetId - Asset ID being transferred
|
|
|
|
receiverPublicKey - Recipient's public key
|
|
|
|
senderPositionId - Sender's position ID
|
|
|
|
receiverPositionId - Recipient's position ID
|
|
|
|
srcFeePositionId - Fee source position ID
|
|
|
|
nonce - Unique transfer identifier
|
|
|
|
amount - Transfer amount
|
|
|
|
expirationTimestamp - Unix timestamp when transfer expires
|
|
|
|
assetIdFee - Fee token asset ID (optional, default '0')
|
|
|
|
maxAmountFee - Maximum fee amount (optional, default '0')
|
|
|
|
Calculation
|
|
The following TypeScript function constructs the transfer message for signing:
|
|
|
|
Copy
|
|
function getTransferMsg({
|
|
assetId,
|
|
receiverPublicKey,
|
|
senderPositionId,
|
|
receiverPositionId,
|
|
srcFeePositionId,
|
|
nonce,
|
|
amount,
|
|
expirationTimestamp,
|
|
assetIdFee = '0',
|
|
maxAmountFee = '0'
|
|
}) {
|
|
// Pack transfer data into 256-bit words
|
|
const w1 = assetId;
|
|
const w2 = assetIdFee;
|
|
const w3 = receiverPublicKey;
|
|
|
|
let w4 = BigInt(senderPositionId);
|
|
w4 = (w4 << 64) + BigInt(receiverPositionId);
|
|
w4 = (w4 << 64) + BigInt(srcFeePositionId);
|
|
w4 = (w4 << 32) + BigInt(nonce);
|
|
|
|
let w5 = BigInt(transfer); // Constant identifier
|
|
w5 = (w5 << 64) + BigInt(amount);
|
|
w5 = (w5 << 64) + BigInt(maxAmountFee);
|
|
w5 = (w5 << 32) + BigInt(expirationTimestamp);
|
|
w5 = w5 << 81;
|
|
|
|
// Calculate message hash
|
|
let msg = pedersen([w1, w2]);
|
|
msg = pedersen([msg, w3]);
|
|
msg = pedersen([msg, w4.toString(16)]);
|
|
return pedersen([msg, w5.toString(16)]);
|
|
}
|
|
For more details on the signature construction, see the StarkEx documentation. |