Compare commits
No commits in common. "main" and "v0.3.0" have entirely different histories.
128
README.md
128
README.md
|
|
@ -20,7 +20,7 @@ The library exposes application-safe public RPC commands, including:
|
||||||
- Marketing records
|
- Marketing records
|
||||||
- On-chain data storage
|
- On-chain data storage
|
||||||
- Governance proposals
|
- Governance proposals
|
||||||
- Transaction creation and broadcasting with application-provided signatures
|
- Transaction creation, signing, and broadcasting
|
||||||
|
|
||||||
Miner relay commands, network mapping changes, monitor-state commands, node
|
Miner relay commands, network mapping changes, monitor-state commands, node
|
||||||
setup synchronization, and server-owner IP controls are intentionally not
|
setup synchronization, and server-owner IP controls are intentionally not
|
||||||
|
|
@ -29,13 +29,14 @@ exposed.
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- PHP 8.1 or newer using a 64-bit build
|
- PHP 8.1 or newer using a 64-bit build
|
||||||
|
- PHP GD
|
||||||
|
- PHP OpenSSL
|
||||||
- The `skein` module from Contractless PHP Modules
|
- The `skein` module from Contractless PHP Modules
|
||||||
- The `oqsphp` module from Contractless PHP Modules
|
- The `oqsphp` module from Contractless PHP Modules
|
||||||
|
|
||||||
Contractless uses the same Falcon key and signature sizes as
|
Contractless uses the same Falcon key and signature sizes as
|
||||||
`Falcon-padded-512`, but its FN-DSA signing mode requires a small message
|
`Falcon-padded-512`, but its FN-DSA signing mode requires a small message
|
||||||
adaptation. `NativeCrypto` performs that adaptation when verifying node
|
adaptation. `NativeCrypto` performs that adaptation automatically.
|
||||||
responses. The application that owns a wallet remains responsible for signing.
|
|
||||||
|
|
||||||
Install and test the Contractless PHP Modules before installing this package:
|
Install and test the Contractless PHP Modules before installing this package:
|
||||||
|
|
||||||
|
|
@ -43,10 +44,10 @@ https://contractless.dev/contractless/Contractless-PHP-Modules
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install version `1.0` through Packagist:
|
Install version `0.3` through Packagist:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
composer require contractless/contractless-php-rpc:^1.0.0 -W
|
composer require contractless/contractless-php-rpc:^0.3.0 -W
|
||||||
```
|
```
|
||||||
|
|
||||||
Composer installs the package under `vendor/` and generates the required
|
Composer installs the package under `vendor/` and generates the required
|
||||||
|
|
@ -61,12 +62,12 @@ require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
use Contractless\Rpc\Client;
|
use Contractless\Rpc\Client;
|
||||||
use Contractless\Rpc\Crypto\NativeCrypto;
|
use Contractless\Rpc\Crypto\NativeCrypto;
|
||||||
use Contractless\Rpc\Protocol\HandshakeProof;
|
|
||||||
use Contractless\Rpc\Transport\StreamTransport;
|
use Contractless\Rpc\Transport\StreamTransport;
|
||||||
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
$handshake = HandshakeProof::fromHex(
|
$credentials = Credentials::fromWalletFile(
|
||||||
$applicationPublicKeyHex,
|
'/private/path/contractless.wallet',
|
||||||
$applicationHandshakeSignatureHex,
|
'wallet decryption key',
|
||||||
);
|
);
|
||||||
|
|
||||||
$crypto = new NativeCrypto();
|
$crypto = new NativeCrypto();
|
||||||
|
|
@ -78,14 +79,10 @@ $client = new Client(
|
||||||
timeout: 10.0,
|
timeout: 10.0,
|
||||||
),
|
),
|
||||||
$crypto,
|
$crypto,
|
||||||
$handshake,
|
$credentials,
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
The handshake signature is the application's 666-byte Falcon signature over
|
|
||||||
the 32-byte Skein-256 digest of `aced`. The RPC package does not create that
|
|
||||||
signature and never receives a wallet file, decryption key, or private key.
|
|
||||||
|
|
||||||
Use port `50050` for the default testnet RPC and `50055` for the default
|
Use port `50050` for the default testnet RPC and `50055` for the default
|
||||||
mainnet RPC unless the node operator configured another port.
|
mainnet RPC unless the node operator configured another port.
|
||||||
|
|
||||||
|
|
@ -127,7 +124,7 @@ $transport = new EndpointPoolTransport([
|
||||||
$client = new Client(
|
$client = new Client(
|
||||||
$transport,
|
$transport,
|
||||||
$crypto,
|
$crypto,
|
||||||
$handshake,
|
$credentials,
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -158,7 +155,7 @@ $selectedTransport = $transport->transportFor($endpointId);
|
||||||
$selectedClient = new Client(
|
$selectedClient = new Client(
|
||||||
$selectedTransport,
|
$selectedTransport,
|
||||||
$crypto,
|
$crypto,
|
||||||
$handshake,
|
$credentials,
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -299,15 +296,17 @@ $result = $client->validateMessage(
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
Wallet registration requires the application to provide the public key and
|
Register the wallet loaded into `$credentials`:
|
||||||
registration signature:
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$result = $client->registerWallet(
|
$result = $client->registerOwnedWallet($address);
|
||||||
$address,
|
```
|
||||||
$publicKeyBytes,
|
|
||||||
$signatureHex,
|
The lower-level registration method is available when an application has
|
||||||
);
|
already created the required signature:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$result = $client->registerWallet($address, $signatureHex);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Token, NFT, RWA, Swap, And Loan RPC
|
## Token, NFT, RWA, Swap, And Loan RPC
|
||||||
|
|
@ -396,26 +395,10 @@ including its voting and activation information.
|
||||||
|
|
||||||
## Create And Broadcast A Transfer
|
## Create And Broadcast A Transfer
|
||||||
|
|
||||||
Transaction builders receive an application-owned signature provider:
|
|
||||||
|
|
||||||
```php
|
|
||||||
use Contractless\Rpc\Signing\CallbackSignatureProvider;
|
|
||||||
|
|
||||||
$signer = new CallbackSignatureProvider(
|
|
||||||
function (string $digest) use ($applicationWallet): string {
|
|
||||||
return $applicationWallet->signDigest($digest);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
The callback receives only the 32-byte transaction digest. How the application
|
|
||||||
loads, decrypts, stores, or remotely accesses its signing key is outside this
|
|
||||||
package. The callback must return a 666-byte raw Falcon signature.
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use Contractless\Rpc\Transaction\TransferBuilder;
|
use Contractless\Rpc\Transaction\TransferBuilder;
|
||||||
|
|
||||||
$transfer = (new TransferBuilder($crypto, $signer))->create(
|
$transfer = (new TransferBuilder($crypto, $credentials))->create(
|
||||||
sender: $sender,
|
sender: $sender,
|
||||||
receiver: $receiver,
|
receiver: $receiver,
|
||||||
coin: 'CLTC',
|
coin: 'CLTC',
|
||||||
|
|
@ -435,7 +418,7 @@ The same builder transfers base currency, tokens, NFTs, and RWAs. Set
|
||||||
```php
|
```php
|
||||||
use Contractless\Rpc\Transaction\AssetBuilder;
|
use Contractless\Rpc\Transaction\AssetBuilder;
|
||||||
|
|
||||||
$assets = new AssetBuilder($crypto, $signer);
|
$assets = new AssetBuilder($crypto, $credentials);
|
||||||
|
|
||||||
$createToken = $assets->createToken(
|
$createToken = $assets->createToken(
|
||||||
creator: $address,
|
creator: $address,
|
||||||
|
|
@ -488,7 +471,7 @@ signs the transaction:
|
||||||
```php
|
```php
|
||||||
use Contractless\Rpc\Transaction\AgreementBuilder;
|
use Contractless\Rpc\Transaction\AgreementBuilder;
|
||||||
|
|
||||||
$agreements = new AgreementBuilder($crypto, $signer);
|
$agreements = new AgreementBuilder($crypto, $credentials);
|
||||||
|
|
||||||
$swap = $agreements->swap(
|
$swap = $agreements->swap(
|
||||||
expiration: time() + 3600,
|
expiration: time() + 3600,
|
||||||
|
|
@ -531,7 +514,8 @@ $exported = json_encode(
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
The second party imports and signs it using its own signature provider:
|
The second party imports and signs it using their own `$crypto` and
|
||||||
|
`$credentials`:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use Contractless\Rpc\Transaction\DualSignedTransaction;
|
use Contractless\Rpc\Transaction\DualSignedTransaction;
|
||||||
|
|
@ -540,7 +524,7 @@ $swap = DualSignedTransaction::import(
|
||||||
json_decode($exported, true, flags: JSON_THROW_ON_ERROR),
|
json_decode($exported, true, flags: JSON_THROW_ON_ERROR),
|
||||||
);
|
);
|
||||||
|
|
||||||
$swap = $swap->signNext($crypto, $secondPartySigner);
|
$swap = $swap->signNext($crypto, $credentials);
|
||||||
|
|
||||||
if ($swap->isComplete()) {
|
if ($swap->isComplete()) {
|
||||||
$client->submitSignedTransaction($swap->toSignedTransaction());
|
$client->submitSignedTransaction($swap->toSignedTransaction());
|
||||||
|
|
@ -552,7 +536,7 @@ if ($swap->isComplete()) {
|
||||||
```php
|
```php
|
||||||
use Contractless\Rpc\Transaction\MiscellaneousBuilder;
|
use Contractless\Rpc\Transaction\MiscellaneousBuilder;
|
||||||
|
|
||||||
$miscellaneous = new MiscellaneousBuilder($crypto, $signer);
|
$miscellaneous = new MiscellaneousBuilder($crypto, $credentials);
|
||||||
|
|
||||||
$payment = $miscellaneous->loanPayment(
|
$payment = $miscellaneous->loanPayment(
|
||||||
amount: 100_000_000,
|
amount: 100_000_000,
|
||||||
|
|
@ -595,7 +579,7 @@ $client->submitSignedTransaction($marketing);
|
||||||
```php
|
```php
|
||||||
use Contractless\Rpc\Transaction\StorageBuilder;
|
use Contractless\Rpc\Transaction\StorageBuilder;
|
||||||
|
|
||||||
$storage = new StorageBuilder($crypto, $signer);
|
$storage = new StorageBuilder($crypto, $credentials);
|
||||||
|
|
||||||
$createKey = $storage->createKey(
|
$createKey = $storage->createKey(
|
||||||
address: $address,
|
address: $address,
|
||||||
|
|
@ -659,7 +643,7 @@ of the preceding string transaction.
|
||||||
```php
|
```php
|
||||||
use Contractless\Rpc\Transaction\GovernanceBuilder;
|
use Contractless\Rpc\Transaction\GovernanceBuilder;
|
||||||
|
|
||||||
$governance = new GovernanceBuilder($crypto, $signer);
|
$governance = new GovernanceBuilder($crypto, $credentials);
|
||||||
|
|
||||||
$proposal = $governance->proposal(
|
$proposal = $governance->proposal(
|
||||||
proposalHash: $proposalDocumentHash,
|
proposalHash: $proposalDocumentHash,
|
||||||
|
|
@ -703,23 +687,42 @@ $unsignedSigningJson = $transaction->unsignedJson;
|
||||||
Use `submitSignedTransaction()` to broadcast the exact bytes without converting
|
Use `submitSignedTransaction()` to broadcast the exact bytes without converting
|
||||||
them to hexadecimal first.
|
them to hexadecimal first.
|
||||||
|
|
||||||
## Wallet Custody
|
## Wallet Files
|
||||||
|
|
||||||
This package does not read Contractless wallet files, decode encrypted private
|
Load a normal Contractless wallet:
|
||||||
key images, accept wallet decryption keys, or store private keys.
|
|
||||||
|
|
||||||
Applications are responsible for:
|
```php
|
||||||
|
$credentials = Credentials::fromWalletFile(
|
||||||
|
'/private/path/contractless.wallet',
|
||||||
|
'wallet decryption key',
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
- Loading or obtaining their wallet material
|
The loader:
|
||||||
- Protecting private keys and decryption keys
|
|
||||||
- Creating the handshake signature
|
|
||||||
- Creating transaction and registration signatures
|
|
||||||
- Supplying only completed signatures and public data to this package
|
|
||||||
|
|
||||||
A faucet may keep its wallet loader in the faucet application. A browser wallet
|
- Decodes every supported Contractless private-key image orientation
|
||||||
may sign locally in the browser. A hosted application may use a hardware or
|
- Verifies the encrypted payload HMAC
|
||||||
remote signer. All three can use the same RPC package because the connection
|
- Decrypts the Falcon private key
|
||||||
layer does not control wallet custody.
|
- Proves that the public and private keys match
|
||||||
|
- Verifies the wallet's canonical short address
|
||||||
|
|
||||||
|
Wallet files and decryption keys must remain outside public web directories,
|
||||||
|
logs, repositories, and client-visible configuration.
|
||||||
|
|
||||||
|
Verify a wallet before using it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php vendor/contractless/contractless-php-rpc/examples/load_wallet.php \
|
||||||
|
/private/path/contractless.wallet \
|
||||||
|
'wallet decryption key'
|
||||||
|
```
|
||||||
|
|
||||||
|
Applications that already manage protected raw keys may load hexadecimal
|
||||||
|
Falcon keys directly:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$credentials = Credentials::fromHex($publicKeyHex, $privateKeyHex);
|
||||||
|
```
|
||||||
|
|
||||||
## Direct Public Command Calls
|
## Direct Public Command Calls
|
||||||
|
|
||||||
|
|
@ -750,7 +753,7 @@ validate and encode the command payload correctly.
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
RPC transport, protocol, and validation failures throw exceptions:
|
RPC transport, protocol, validation, and wallet failures throw exceptions:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
try {
|
try {
|
||||||
|
|
@ -760,7 +763,8 @@ try {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not expose internal node replies directly to public users.
|
Do not expose wallet errors, filesystem paths, private material, or internal
|
||||||
|
node replies directly to public users.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.1",
|
"php": ">=8.1",
|
||||||
|
"ext-gd": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
|
"ext-openssl": "*",
|
||||||
"ext-oqsphp": "*",
|
"ext-oqsphp": "*",
|
||||||
"ext-skein": "*"
|
"ext-skein": "*"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"_readme": [
|
|
||||||
"This file locks the dependencies of your project to a known state",
|
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
|
||||||
"This file is @generated automatically"
|
|
||||||
],
|
|
||||||
"content-hash": "9177fad695a02a8cb027a5064822e3cb",
|
|
||||||
"packages": [],
|
|
||||||
"packages-dev": [],
|
|
||||||
"aliases": [],
|
|
||||||
"minimum-stability": "stable",
|
|
||||||
"stability-flags": [],
|
|
||||||
"prefer-stable": false,
|
|
||||||
"prefer-lowest": false,
|
|
||||||
"platform": {
|
|
||||||
"php": ">=8.1",
|
|
||||||
"ext-json": "*",
|
|
||||||
"ext-oqsphp": "*",
|
|
||||||
"ext-skein": "*"
|
|
||||||
},
|
|
||||||
"platform-dev": [],
|
|
||||||
"plugin-api-version": "2.6.0"
|
|
||||||
}
|
|
||||||
|
|
@ -9,9 +9,9 @@ use Contractless\Rpc\Exception\ProtocolException;
|
||||||
use Contractless\Rpc\Protocol\Address;
|
use Contractless\Rpc\Protocol\Address;
|
||||||
use Contractless\Rpc\Protocol\Binary;
|
use Contractless\Rpc\Protocol\Binary;
|
||||||
use Contractless\Rpc\Protocol\Command;
|
use Contractless\Rpc\Protocol\Command;
|
||||||
use Contractless\Rpc\Protocol\HandshakeProof;
|
|
||||||
use Contractless\Rpc\Protocol\RequestBuilder;
|
use Contractless\Rpc\Protocol\RequestBuilder;
|
||||||
use Contractless\Rpc\Transport\TransportInterface;
|
use Contractless\Rpc\Transport\TransportInterface;
|
||||||
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
final class Client
|
final class Client
|
||||||
{
|
{
|
||||||
|
|
@ -26,7 +26,7 @@ final class Client
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly TransportInterface $transport,
|
private readonly TransportInterface $transport,
|
||||||
private readonly CryptoInterface $crypto,
|
private readonly CryptoInterface $crypto,
|
||||||
private readonly HandshakeProof $handshakeProof,
|
private readonly Credentials $credentials,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,21 +175,29 @@ final class Client
|
||||||
return $this->call(Command::CONTRACT_BY_ADDRESS, Address::toBytes($address));
|
return $this->call(Command::CONTRACT_BY_ADDRESS, Address::toBytes($address));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerWallet(
|
public function registerWallet(string $address, string $signature): string
|
||||||
string $address,
|
|
||||||
string $publicKey,
|
|
||||||
string $signature,
|
|
||||||
): string
|
|
||||||
{
|
{
|
||||||
Binary::length($publicKey, 897, 'Falcon public key');
|
|
||||||
return $this->call(
|
return $this->call(
|
||||||
Command::REGISTER_WALLET,
|
Command::REGISTER_WALLET,
|
||||||
Address::toBytes($address)
|
Address::toBytes($address)
|
||||||
. $publicKey
|
. $this->credentials->publicKey
|
||||||
. RequestBuilder::signature($signature),
|
. RequestBuilder::signature($signature),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function registerOwnedWallet(string $address): string
|
||||||
|
{
|
||||||
|
$signedPayload = Binary::u8(Command::REGISTER_WALLET)
|
||||||
|
. Address::toBytes($address)
|
||||||
|
. $this->credentials->publicKey;
|
||||||
|
$signature = $this->crypto->sign(
|
||||||
|
$this->crypto->skein256($signedPayload),
|
||||||
|
$this->credentials->privateKey,
|
||||||
|
$this->credentials->publicKey,
|
||||||
|
);
|
||||||
|
return $this->registerWallet($address, bin2hex($signature));
|
||||||
|
}
|
||||||
|
|
||||||
public function vanityLookup(string $address): string
|
public function vanityLookup(string $address): string
|
||||||
{
|
{
|
||||||
return $this->call(Command::VANITY_LOOKUP, Address::toBytes($address));
|
return $this->call(Command::VANITY_LOOKUP, Address::toBytes($address));
|
||||||
|
|
@ -283,9 +291,16 @@ final class Client
|
||||||
|
|
||||||
private function buildHandshake(): string
|
private function buildHandshake(): string
|
||||||
{
|
{
|
||||||
|
$digest = $this->crypto->skein256('aced');
|
||||||
|
$signature = $this->crypto->sign(
|
||||||
|
$digest,
|
||||||
|
$this->credentials->privateKey,
|
||||||
|
$this->credentials->publicKey,
|
||||||
|
);
|
||||||
|
|
||||||
return self::CLIENT_MARKER
|
return self::CLIENT_MARKER
|
||||||
. $this->handshakeProof->signature
|
. $signature
|
||||||
. $this->handshakeProof->publicKey
|
. $this->credentials->publicKey
|
||||||
. Binary::u32(time())
|
. Binary::u32(time())
|
||||||
. str_repeat("\0", 18);
|
. str_repeat("\0", 18);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ final class CallbackCrypto implements CryptoInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Closure $skein,
|
private readonly Closure $skein,
|
||||||
|
private readonly Closure $signer,
|
||||||
private readonly Closure $verifier,
|
private readonly Closure $verifier,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
@ -24,6 +25,15 @@ final class CallbackCrypto implements CryptoInterface
|
||||||
return $digest;
|
return $digest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sign(string $message, string $privateKey, string $publicKey): string
|
||||||
|
{
|
||||||
|
$signature = ($this->signer)($message, $privateKey, $publicKey);
|
||||||
|
if (!is_string($signature) || strlen($signature) !== 666) {
|
||||||
|
throw new ProtocolException('The signing callback must return exactly 666 raw bytes.');
|
||||||
|
}
|
||||||
|
return $signature;
|
||||||
|
}
|
||||||
|
|
||||||
public function verify(string $message, string $signature, string $publicKey): bool
|
public function verify(string $message, string $signature, string $publicKey): bool
|
||||||
{
|
{
|
||||||
return (bool) ($this->verifier)($message, $signature, $publicKey);
|
return (bool) ($this->verifier)($message, $signature, $publicKey);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ interface CryptoInterface
|
||||||
/** Return the raw 32-byte Skein-256 digest. */
|
/** Return the raw 32-byte Skein-256 digest. */
|
||||||
public function skein256(string $message): string;
|
public function skein256(string $message): string;
|
||||||
|
|
||||||
|
/** Sign raw Contractless message bytes and return a 666-byte signature. */
|
||||||
|
public function sign(string $message, string $privateKey, string $publicKey): string;
|
||||||
|
|
||||||
/** Verify raw Contractless message bytes against a 666-byte signature. */
|
/** Verify raw Contractless message bytes against a 666-byte signature. */
|
||||||
public function verify(
|
public function verify(
|
||||||
string $message,
|
string $message,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,19 @@ final class NativeCrypto implements CryptoInterface
|
||||||
return $digest;
|
return $digest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sign(string $message, string $privateKey, string $publicKey): string
|
||||||
|
{
|
||||||
|
Binary::length($privateKey, 1281, 'Falcon private key');
|
||||||
|
Binary::length($publicKey, 897, 'Falcon public key');
|
||||||
|
$adapted = $this->adaptMessage($message, $publicKey);
|
||||||
|
$signature = '';
|
||||||
|
$status = $this->falcon->sign($signature, $adapted, $privateKey);
|
||||||
|
if ($status !== 0 || strlen($signature) !== 666) {
|
||||||
|
throw new ContractlessException('Falcon-padded-512 signing failed.');
|
||||||
|
}
|
||||||
|
return $signature;
|
||||||
|
}
|
||||||
|
|
||||||
public function verify(string $message, string $signature, string $publicKey): bool
|
public function verify(string $message, string $signature, string $publicKey): bool
|
||||||
{
|
{
|
||||||
Binary::length($signature, 666, 'Falcon signature');
|
Binary::length($signature, 666, 'Falcon signature');
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Contractless\Rpc\Protocol;
|
|
||||||
|
|
||||||
final class HandshakeProof
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
public readonly string $publicKey,
|
|
||||||
public readonly string $signature,
|
|
||||||
) {
|
|
||||||
Binary::length($publicKey, 897, 'Falcon public key');
|
|
||||||
Binary::length($signature, 666, 'Falcon handshake signature');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fromHex(string $publicKey, string $signature): self
|
|
||||||
{
|
|
||||||
return new self(
|
|
||||||
Binary::hex($publicKey, 897, 'Falcon public key'),
|
|
||||||
Binary::hex($signature, 666, 'Falcon handshake signature'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Contractless\Rpc\Signing;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Contractless\Rpc\Exception\ProtocolException;
|
|
||||||
|
|
||||||
final class CallbackSignatureProvider implements SignatureProviderInterface
|
|
||||||
{
|
|
||||||
public function __construct(private readonly Closure $signer)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function sign(string $digest): string
|
|
||||||
{
|
|
||||||
if (strlen($digest) !== 32) {
|
|
||||||
throw new ProtocolException('Signing requires a 32-byte digest.');
|
|
||||||
}
|
|
||||||
$signature = ($this->signer)($digest);
|
|
||||||
if (!is_string($signature) || strlen($signature) !== 666) {
|
|
||||||
throw new ProtocolException(
|
|
||||||
'The signing callback must return exactly 666 raw bytes.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $signature;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Contractless\Rpc\Signing;
|
|
||||||
|
|
||||||
interface SignatureProviderInterface
|
|
||||||
{
|
|
||||||
/** Sign a raw 32-byte Contractless digest and return 666 raw bytes. */
|
|
||||||
public function sign(string $digest): string;
|
|
||||||
}
|
|
||||||
|
|
@ -8,13 +8,13 @@ use Contractless\Rpc\Crypto\CryptoInterface;
|
||||||
use Contractless\Rpc\Exception\ProtocolException;
|
use Contractless\Rpc\Exception\ProtocolException;
|
||||||
use Contractless\Rpc\Protocol\Address;
|
use Contractless\Rpc\Protocol\Address;
|
||||||
use Contractless\Rpc\Protocol\Binary;
|
use Contractless\Rpc\Protocol\Binary;
|
||||||
use Contractless\Rpc\Signing\SignatureProviderInterface;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
final class AgreementBuilder
|
final class AgreementBuilder
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly CryptoInterface $crypto,
|
private readonly CryptoInterface $crypto,
|
||||||
private readonly SignatureProviderInterface $signer,
|
private readonly Credentials $credentials,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ final class AgreementBuilder
|
||||||
. Binary::u64($tip1) . Binary::u64($tip2)
|
. Binary::u64($tip1) . Binary::u64($tip2)
|
||||||
. Binary::u64($fee1) . Binary::u64($fee2);
|
. Binary::u64($fee1) . Binary::u64($fee2);
|
||||||
return (new DualSignedTransaction($json, $bytes, $hash))
|
return (new DualSignedTransaction($json, $bytes, $hash))
|
||||||
->signNext($this->crypto, $this->signer);
|
->signNext($this->crypto, $this->credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loan(
|
public function loan(
|
||||||
|
|
@ -96,7 +96,7 @@ final class AgreementBuilder
|
||||||
. Binary::u8($gracePeriod) . Binary::u64($maxLateValue)
|
. Binary::u8($gracePeriod) . Binary::u64($maxLateValue)
|
||||||
. Binary::u64($fee);
|
. Binary::u64($fee);
|
||||||
return (new DualSignedTransaction($json, $bytes, $hash, storesHash: true))
|
return (new DualSignedTransaction($json, $bytes, $hash, storesHash: true))
|
||||||
->signNext($this->crypto, $this->signer);
|
->signNext($this->crypto, $this->credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function json(array $unsigned): string
|
private function json(array $unsigned): string
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,16 @@ namespace Contractless\Rpc\Transaction;
|
||||||
use Contractless\Rpc\Crypto\CryptoInterface;
|
use Contractless\Rpc\Crypto\CryptoInterface;
|
||||||
use Contractless\Rpc\Protocol\Address;
|
use Contractless\Rpc\Protocol\Address;
|
||||||
use Contractless\Rpc\Protocol\Binary;
|
use Contractless\Rpc\Protocol\Binary;
|
||||||
use Contractless\Rpc\Signing\SignatureProviderInterface;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
final class AssetBuilder
|
final class AssetBuilder
|
||||||
{
|
{
|
||||||
use SignsTransactions;
|
use SignsTransactions;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(CryptoInterface $crypto, Credentials $credentials)
|
||||||
CryptoInterface $crypto,
|
|
||||||
SignatureProviderInterface $signer,
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
$this->crypto = $crypto;
|
$this->crypto = $crypto;
|
||||||
$this->signer = $signer;
|
$this->credentials = $credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createToken(
|
public function createToken(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace Contractless\Rpc\Transaction;
|
||||||
|
|
||||||
use Contractless\Rpc\Crypto\CryptoInterface;
|
use Contractless\Rpc\Crypto\CryptoInterface;
|
||||||
use Contractless\Rpc\Exception\ProtocolException;
|
use Contractless\Rpc\Exception\ProtocolException;
|
||||||
use Contractless\Rpc\Signing\SignatureProviderInterface;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
final class DualSignedTransaction
|
final class DualSignedTransaction
|
||||||
{
|
{
|
||||||
|
|
@ -20,10 +20,7 @@ final class DualSignedTransaction
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function signNext(
|
public function signNext(CryptoInterface $crypto, Credentials $credentials): self
|
||||||
CryptoInterface $crypto,
|
|
||||||
SignatureProviderInterface $signer,
|
|
||||||
): self
|
|
||||||
{
|
{
|
||||||
$recalculatedHash = $crypto->skein256($this->unsignedJson);
|
$recalculatedHash = $crypto->skein256($this->unsignedJson);
|
||||||
if (!hash_equals($this->hash, $recalculatedHash)) {
|
if (!hash_equals($this->hash, $recalculatedHash)) {
|
||||||
|
|
@ -31,7 +28,7 @@ final class DualSignedTransaction
|
||||||
'The exported transaction hash does not match its unsigned transaction.',
|
'The exported transaction hash does not match its unsigned transaction.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$signature = $signer->sign($this->hash);
|
$signature = $crypto->sign($this->hash, $credentials->privateKey, $credentials->publicKey);
|
||||||
if ($this->signature1 === '') {
|
if ($this->signature1 === '') {
|
||||||
return new self(
|
return new self(
|
||||||
$this->unsignedJson,
|
$this->unsignedJson,
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,16 @@ use Contractless\Rpc\Crypto\CryptoInterface;
|
||||||
use Contractless\Rpc\Protocol\Address;
|
use Contractless\Rpc\Protocol\Address;
|
||||||
use Contractless\Rpc\Protocol\Binary;
|
use Contractless\Rpc\Protocol\Binary;
|
||||||
use Contractless\Rpc\Protocol\RequestBuilder;
|
use Contractless\Rpc\Protocol\RequestBuilder;
|
||||||
use Contractless\Rpc\Signing\SignatureProviderInterface;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
final class GovernanceBuilder
|
final class GovernanceBuilder
|
||||||
{
|
{
|
||||||
use SignsTransactions;
|
use SignsTransactions;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(CryptoInterface $crypto, Credentials $credentials)
|
||||||
CryptoInterface $crypto,
|
|
||||||
SignatureProviderInterface $signer,
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
$this->crypto = $crypto;
|
$this->crypto = $crypto;
|
||||||
$this->signer = $signer;
|
$this->credentials = $credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function proposal(
|
public function proposal(
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,16 @@ use Contractless\Rpc\Crypto\CryptoInterface;
|
||||||
use Contractless\Rpc\Protocol\Address;
|
use Contractless\Rpc\Protocol\Address;
|
||||||
use Contractless\Rpc\Protocol\Binary;
|
use Contractless\Rpc\Protocol\Binary;
|
||||||
use Contractless\Rpc\Protocol\RequestBuilder;
|
use Contractless\Rpc\Protocol\RequestBuilder;
|
||||||
use Contractless\Rpc\Signing\SignatureProviderInterface;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
final class MiscellaneousBuilder
|
final class MiscellaneousBuilder
|
||||||
{
|
{
|
||||||
use SignsTransactions;
|
use SignsTransactions;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(CryptoInterface $crypto, Credentials $credentials)
|
||||||
CryptoInterface $crypto,
|
|
||||||
SignatureProviderInterface $signer,
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
$this->crypto = $crypto;
|
$this->crypto = $crypto;
|
||||||
$this->signer = $signer;
|
$this->credentials = $credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function marketing(
|
public function marketing(
|
||||||
|
|
@ -91,7 +88,11 @@ final class MiscellaneousBuilder
|
||||||
JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
|
JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
|
||||||
);
|
);
|
||||||
$hash = $this->crypto->skein256($json);
|
$hash = $this->crypto->skein256($json);
|
||||||
$signature = $this->signer->sign($hash);
|
$signature = $this->crypto->sign(
|
||||||
|
$hash,
|
||||||
|
$this->credentials->privateKey,
|
||||||
|
$this->credentials->publicKey,
|
||||||
|
);
|
||||||
$binary = Binary::u8(8) . Binary::u32($timestamp) . Binary::u64($amount)
|
$binary = Binary::u8(8) . Binary::u32($timestamp) . Binary::u64($amount)
|
||||||
. RequestBuilder::hash($contractHash, 'Loan contract hash')
|
. RequestBuilder::hash($contractHash, 'Loan contract hash')
|
||||||
. Address::toBytes($address) . Binary::u64($tip) . Binary::u64($fee)
|
. Address::toBytes($address) . Binary::u64($tip) . Binary::u64($fee)
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ declare(strict_types=1);
|
||||||
namespace Contractless\Rpc\Transaction;
|
namespace Contractless\Rpc\Transaction;
|
||||||
|
|
||||||
use Contractless\Rpc\Crypto\CryptoInterface;
|
use Contractless\Rpc\Crypto\CryptoInterface;
|
||||||
use Contractless\Rpc\Signing\SignatureProviderInterface;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
trait SignsTransactions
|
trait SignsTransactions
|
||||||
{
|
{
|
||||||
private CryptoInterface $crypto;
|
private CryptoInterface $crypto;
|
||||||
private SignatureProviderInterface $signer;
|
private Credentials $credentials;
|
||||||
|
|
||||||
private function sign(array $unsigned, string $binary): SignedTransaction
|
private function sign(array $unsigned, string $binary): SignedTransaction
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +19,11 @@ trait SignsTransactions
|
||||||
JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
|
JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
|
||||||
);
|
);
|
||||||
$digest = $this->crypto->skein256($json);
|
$digest = $this->crypto->skein256($json);
|
||||||
$signature = $this->signer->sign($digest);
|
$signature = $this->crypto->sign(
|
||||||
|
$digest,
|
||||||
|
$this->credentials->privateKey,
|
||||||
|
$this->credentials->publicKey,
|
||||||
|
);
|
||||||
return new SignedTransaction($binary . $signature, $signature, $json);
|
return new SignedTransaction($binary . $signature, $signature, $json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,16 @@ use Contractless\Rpc\Exception\ProtocolException;
|
||||||
use Contractless\Rpc\Protocol\Address;
|
use Contractless\Rpc\Protocol\Address;
|
||||||
use Contractless\Rpc\Protocol\Binary;
|
use Contractless\Rpc\Protocol\Binary;
|
||||||
use Contractless\Rpc\Protocol\RequestBuilder;
|
use Contractless\Rpc\Protocol\RequestBuilder;
|
||||||
use Contractless\Rpc\Signing\SignatureProviderInterface;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
final class StorageBuilder
|
final class StorageBuilder
|
||||||
{
|
{
|
||||||
use SignsTransactions;
|
use SignsTransactions;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(CryptoInterface $crypto, Credentials $credentials)
|
||||||
CryptoInterface $crypto,
|
|
||||||
SignatureProviderInterface $signer,
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
$this->crypto = $crypto;
|
$this->crypto = $crypto;
|
||||||
$this->signer = $signer;
|
$this->credentials = $credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createKey(
|
public function createKey(
|
||||||
|
|
@ -209,7 +206,11 @@ final class StorageBuilder
|
||||||
. ',"address":' . json_encode($address, JSON_THROW_ON_ERROR)
|
. ',"address":' . json_encode($address, JSON_THROW_ON_ERROR)
|
||||||
. ',"txfee":' . $fee . '}';
|
. ',"txfee":' . $fee . '}';
|
||||||
$digest = $this->crypto->skein256($json);
|
$digest = $this->crypto->skein256($json);
|
||||||
$signature = $this->signer->sign($digest);
|
$signature = $this->crypto->sign(
|
||||||
|
$digest,
|
||||||
|
$this->credentials->privateKey,
|
||||||
|
$this->credentials->publicKey,
|
||||||
|
);
|
||||||
$binary = Binary::u8($type) . Binary::u32($timestamp)
|
$binary = Binary::u8($type) . Binary::u32($timestamp)
|
||||||
. RequestBuilder::hash($storageKey, 'Storage key') . $key . $valueBytes
|
. RequestBuilder::hash($storageKey, 'Storage key') . $key . $valueBytes
|
||||||
. Address::toBytes($address) . Binary::u64($fee);
|
. Address::toBytes($address) . Binary::u64($fee);
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ namespace Contractless\Rpc\Transaction;
|
||||||
use Contractless\Rpc\Crypto\CryptoInterface;
|
use Contractless\Rpc\Crypto\CryptoInterface;
|
||||||
use Contractless\Rpc\Protocol\Address;
|
use Contractless\Rpc\Protocol\Address;
|
||||||
use Contractless\Rpc\Protocol\Binary;
|
use Contractless\Rpc\Protocol\Binary;
|
||||||
use Contractless\Rpc\Signing\SignatureProviderInterface;
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
final class TransferBuilder
|
final class TransferBuilder
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly CryptoInterface $crypto,
|
private readonly CryptoInterface $crypto,
|
||||||
private readonly SignatureProviderInterface $signer,
|
private readonly Credentials $credentials,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,7 +42,11 @@ final class TransferBuilder
|
||||||
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
$digest = $this->crypto->skein256($unsigned);
|
$digest = $this->crypto->skein256($unsigned);
|
||||||
$signature = $this->signer->sign($digest);
|
$signature = $this->crypto->sign(
|
||||||
|
$digest,
|
||||||
|
$this->credentials->privateKey,
|
||||||
|
$this->credentials->publicKey,
|
||||||
|
);
|
||||||
|
|
||||||
$bytes = Binary::u8(2)
|
$bytes = Binary::u8(2)
|
||||||
. Binary::u32($timestamp)
|
. Binary::u32($timestamp)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Contractless\Rpc\Transport;
|
|
||||||
|
|
||||||
use Contractless\Rpc\Exception\TransportException;
|
|
||||||
|
|
||||||
interface EndpointHealthTrackerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Remove endpoints that should not receive requests at this time.
|
|
||||||
*
|
|
||||||
* @param list<string> $endpointIds
|
|
||||||
* @return list<string>
|
|
||||||
*/
|
|
||||||
public function eligibleEndpointIds(array $endpointIds): array;
|
|
||||||
|
|
||||||
public function recordSuccess(string $endpointId): void;
|
|
||||||
|
|
||||||
public function recordFailure(
|
|
||||||
string $endpointId,
|
|
||||||
TransportException $error,
|
|
||||||
): void;
|
|
||||||
}
|
|
||||||
|
|
@ -25,11 +25,8 @@ final class EndpointPoolTransport implements TransportInterface
|
||||||
/**
|
/**
|
||||||
* @param array<string, TransportInterface> $transports
|
* @param array<string, TransportInterface> $transports
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(array $transports, private readonly bool $randomize = true)
|
||||||
array $transports,
|
{
|
||||||
private readonly bool $randomize = true,
|
|
||||||
private readonly ?EndpointHealthTrackerInterface $healthTracker = null,
|
|
||||||
) {
|
|
||||||
if ($transports === []) {
|
if ($transports === []) {
|
||||||
throw new InvalidArgumentException('The RPC endpoint pool cannot be empty.');
|
throw new InvalidArgumentException('The RPC endpoint pool cannot be empty.');
|
||||||
}
|
}
|
||||||
|
|
@ -58,14 +55,6 @@ final class EndpointPoolTransport implements TransportInterface
|
||||||
): string {
|
): string {
|
||||||
$this->lastSuccessfulEndpointId = null;
|
$this->lastSuccessfulEndpointId = null;
|
||||||
$endpointIds = array_keys($this->transports);
|
$endpointIds = array_keys($this->transports);
|
||||||
if ($this->healthTracker !== null) {
|
|
||||||
$endpointIds = $this->healthTracker->eligibleEndpointIds($endpointIds);
|
|
||||||
}
|
|
||||||
if ($endpointIds === []) {
|
|
||||||
throw new TransportException(
|
|
||||||
'Every Contractless RPC endpoint is temporarily unavailable.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ($this->randomize && count($endpointIds) > 1) {
|
if ($this->randomize && count($endpointIds) > 1) {
|
||||||
shuffle($endpointIds);
|
shuffle($endpointIds);
|
||||||
}
|
}
|
||||||
|
|
@ -82,12 +71,10 @@ final class EndpointPoolTransport implements TransportInterface
|
||||||
$validateHandshake,
|
$validateHandshake,
|
||||||
);
|
);
|
||||||
$this->lastSuccessfulEndpointId = $endpointId;
|
$this->lastSuccessfulEndpointId = $endpointId;
|
||||||
$this->healthTracker?->recordSuccess($endpointId);
|
|
||||||
return $reply;
|
return $reply;
|
||||||
} catch (TransportException $error) {
|
} catch (TransportException $error) {
|
||||||
// Only transport failures move the same request to another
|
// Only transport failures move the same request to another
|
||||||
// endpoint. Protocol and validation errors remain final.
|
// endpoint. Protocol and validation errors remain final.
|
||||||
$this->healthTracker?->recordFailure($endpointId, $error);
|
|
||||||
$failures[] = "$endpointId: {$error->getMessage()}";
|
$failures[] = "$endpointId: {$error->getMessage()}";
|
||||||
$lastFailure = $error;
|
$lastFailure = $error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ use Contractless\Rpc\Exception\ProtocolException;
|
||||||
use Contractless\Rpc\Exception\TransportException;
|
use Contractless\Rpc\Exception\TransportException;
|
||||||
use Contractless\Rpc\Protocol\Address;
|
use Contractless\Rpc\Protocol\Address;
|
||||||
use Contractless\Rpc\Protocol\Command;
|
use Contractless\Rpc\Protocol\Command;
|
||||||
use Contractless\Rpc\Protocol\HandshakeProof;
|
|
||||||
use Contractless\Rpc\Signing\CallbackSignatureProvider;
|
|
||||||
use Contractless\Rpc\Transport\EndpointPoolTransport;
|
use Contractless\Rpc\Transport\EndpointPoolTransport;
|
||||||
use Contractless\Rpc\Transport\TransportInterface;
|
use Contractless\Rpc\Transport\TransportInterface;
|
||||||
use Contractless\Rpc\Transaction\TransferBuilder;
|
use Contractless\Rpc\Transaction\TransferBuilder;
|
||||||
|
|
@ -32,6 +30,7 @@ use Contractless\Rpc\Transaction\StorageBuilder;
|
||||||
use Contractless\Rpc\Transaction\MiscellaneousBuilder;
|
use Contractless\Rpc\Transaction\MiscellaneousBuilder;
|
||||||
use Contractless\Rpc\Transaction\AgreementBuilder;
|
use Contractless\Rpc\Transaction\AgreementBuilder;
|
||||||
use Contractless\Rpc\Transaction\DualSignedTransaction;
|
use Contractless\Rpc\Transaction\DualSignedTransaction;
|
||||||
|
use Contractless\Rpc\Wallet\Credentials;
|
||||||
|
|
||||||
function expect(bool $condition, string $message): void
|
function expect(bool $condition, string $message): void
|
||||||
{
|
{
|
||||||
|
|
@ -88,16 +87,11 @@ $transport = new class implements TransportInterface {
|
||||||
|
|
||||||
$crypto = new CallbackCrypto(
|
$crypto = new CallbackCrypto(
|
||||||
fn (string $message): string => str_repeat("\x11", 32),
|
fn (string $message): string => str_repeat("\x11", 32),
|
||||||
|
fn (string $message, string $privateKey, string $publicKey): string => str_repeat("\x22", 666),
|
||||||
fn (string $message, string $signature, string $publicKey): bool => true,
|
fn (string $message, string $signature, string $publicKey): bool => true,
|
||||||
);
|
);
|
||||||
$signer = new CallbackSignatureProvider(
|
$credentials = new Credentials(str_repeat("\x33", 897), str_repeat("\x44", 1281));
|
||||||
fn (string $digest): string => str_repeat("\x22", 666),
|
$client = new Client($transport, $crypto, $credentials);
|
||||||
);
|
|
||||||
$handshakeProof = new HandshakeProof(
|
|
||||||
str_repeat("\x33", 897),
|
|
||||||
str_repeat("\x22", 666),
|
|
||||||
);
|
|
||||||
$client = new Client($transport, $crypto, $handshakeProof);
|
|
||||||
|
|
||||||
expect($client->blockByHeight(42) === 'ok', 'Client did not return the RPC payload.');
|
expect($client->blockByHeight(42) === 'ok', 'Client did not return the RPC payload.');
|
||||||
expect(strlen($transport->handshake) === 1587, 'Handshake request has the wrong size.');
|
expect(strlen($transport->handshake) === 1587, 'Handshake request has the wrong size.');
|
||||||
|
|
@ -109,15 +103,11 @@ $client->storageLookupCost(str_repeat('01', 32), 'all', str_repeat('ab', 20) . '
|
||||||
expect(ord($transport->request[0]) === Command::STORAGE_LOOKUP_COST, 'Storage command is wrong.');
|
expect(ord($transport->request[0]) === Command::STORAGE_LOOKUP_COST, 'Storage command is wrong.');
|
||||||
expect(strlen($transport->request) === 4 + 32 + 50 + 22, 'Storage cost payload has the wrong size.');
|
expect(strlen($transport->request) === 4 + 32 + 50 + 22, 'Storage cost payload has the wrong size.');
|
||||||
|
|
||||||
$client->registerWallet(
|
$client->registerOwnedWallet(str_repeat('ab', 20) . '.cltc');
|
||||||
str_repeat('ab', 20) . '.cltc',
|
|
||||||
$handshakeProof->publicKey,
|
|
||||||
str_repeat('22', 666),
|
|
||||||
);
|
|
||||||
expect(ord($transport->request[0]) === Command::REGISTER_WALLET, 'Wallet registration command is wrong.');
|
expect(ord($transport->request[0]) === Command::REGISTER_WALLET, 'Wallet registration command is wrong.');
|
||||||
expect(strlen($transport->request) === 4 + 22 + 897 + 666, 'Wallet registration payload has the wrong size.');
|
expect(strlen($transport->request) === 4 + 22 + 897 + 666, 'Wallet registration payload has the wrong size.');
|
||||||
|
|
||||||
$transfer = (new TransferBuilder($crypto, $signer))->create(
|
$transfer = (new TransferBuilder($crypto, $credentials))->create(
|
||||||
str_repeat('ab', 20) . '.cltc',
|
str_repeat('ab', 20) . '.cltc',
|
||||||
str_repeat('cd', 20) . '.cltc',
|
str_repeat('cd', 20) . '.cltc',
|
||||||
'CLTC',
|
'CLTC',
|
||||||
|
|
@ -137,19 +127,19 @@ expect(
|
||||||
);
|
);
|
||||||
|
|
||||||
$address = str_repeat('ab', 20) . '.cltc';
|
$address = str_repeat('ab', 20) . '.cltc';
|
||||||
$assets = new AssetBuilder($crypto, $signer);
|
$assets = new AssetBuilder($crypto, $credentials);
|
||||||
expect(strlen($assets->createToken($address, 'TEST', 1000, true, 100, 1)->bytes) === 725, 'Token size is wrong.');
|
expect(strlen($assets->createToken($address, 'TEST', 1000, true, 100, 1)->bytes) === 725, 'Token size is wrong.');
|
||||||
expect(strlen($assets->issueToken($address, 'TEST', 1000, 100, 1)->bytes) === 724, 'Token issue size is wrong.');
|
expect(strlen($assets->issueToken($address, 'TEST', 1000, 100, 1)->bytes) === 724, 'Token issue size is wrong.');
|
||||||
expect(strlen($assets->createNft($address, false, false, 'ART', 'cid', 1, 'description', 100, 1)->bytes) === 922, 'NFT size is wrong.');
|
expect(strlen($assets->createNft($address, false, false, 'ART', 'cid', 1, 'description', 100, 1)->bytes) === 922, 'NFT size is wrong.');
|
||||||
expect(strlen($assets->burn($address, 'TEST', 0, 1, 100, 1)->bytes) === 728, 'Burn size is wrong.');
|
expect(strlen($assets->burn($address, 'TEST', 0, 1, 100, 1)->bytes) === 728, 'Burn size is wrong.');
|
||||||
expect(strlen($assets->vanity($address, str_repeat('a', 20) . '.cltc', 100, 1)->bytes) === 723, 'Vanity size is wrong.');
|
expect(strlen($assets->vanity($address, str_repeat('a', 20) . '.cltc', 100, 1)->bytes) === 723, 'Vanity size is wrong.');
|
||||||
|
|
||||||
$governance = new GovernanceBuilder($crypto, $signer);
|
$governance = new GovernanceBuilder($crypto, $credentials);
|
||||||
expect(strlen($governance->proposal(str_repeat('01', 32), $address, 100, 1)->bytes) === 733, 'Proposal size is wrong.');
|
expect(strlen($governance->proposal(str_repeat('01', 32), $address, 100, 1)->bytes) === 733, 'Proposal size is wrong.');
|
||||||
expect(strlen($governance->proposalVote(str_repeat('01', 32), $address, true, 100, 1)->bytes) === 734, 'Proposal vote size is wrong.');
|
expect(strlen($governance->proposalVote(str_repeat('01', 32), $address, true, 100, 1)->bytes) === 734, 'Proposal vote size is wrong.');
|
||||||
expect(strlen($governance->activationVote(str_repeat('01', 32), str_repeat('02', 32), 'CLP-0001.md', $address, true, 100, 1)->bytes) === 866, 'Activation vote size is wrong.');
|
expect(strlen($governance->activationVote(str_repeat('01', 32), str_repeat('02', 32), 'CLP-0001.md', $address, true, 100, 1)->bytes) === 866, 'Activation vote size is wrong.');
|
||||||
|
|
||||||
$storage = new StorageBuilder($crypto, $signer);
|
$storage = new StorageBuilder($crypto, $credentials);
|
||||||
expect(strlen($storage->createKey($address, 100, 1)->bytes) === 701, 'Storage key size is wrong.');
|
expect(strlen($storage->createKey($address, 100, 1)->bytes) === 701, 'Storage key size is wrong.');
|
||||||
expect(strlen($storage->boolean(str_repeat('01', 32), 'active', true, $address, 100, 1)->bytes) === 784, 'Storage bool size is wrong.');
|
expect(strlen($storage->boolean(str_repeat('01', 32), 'active', true, $address, 100, 1)->bytes) === 784, 'Storage bool size is wrong.');
|
||||||
expect(strlen($storage->u32(str_repeat('01', 32), 'count', 42, $address, 100, 1)->bytes) === 787, 'Storage u32 size is wrong.');
|
expect(strlen($storage->u32(str_repeat('01', 32), 'count', 42, $address, 100, 1)->bytes) === 787, 'Storage u32 size is wrong.');
|
||||||
|
|
@ -160,19 +150,19 @@ expect(bin2hex(substr($storage->u128(str_repeat('01', 32), 'large', '34028236692
|
||||||
expect(strlen($storage->string(str_repeat('01', 32), 'bio', 'hello', $address, str_repeat('0', 64), 100, 1)->bytes) === 995, 'Storage string size is wrong.');
|
expect(strlen($storage->string(str_repeat('01', 32), 'bio', 'hello', $address, str_repeat('0', 64), 100, 1)->bytes) === 995, 'Storage string size is wrong.');
|
||||||
expect(strlen($storage->delete(str_repeat('01', 32), 'bio', $address, 100, 1)->bytes) === 783, 'Storage delete size is wrong.');
|
expect(strlen($storage->delete(str_repeat('01', 32), 'bio', $address, 100, 1)->bytes) === 783, 'Storage delete size is wrong.');
|
||||||
|
|
||||||
$misc = new MiscellaneousBuilder($crypto, $signer);
|
$misc = new MiscellaneousBuilder($crypto, $credentials);
|
||||||
expect(strlen($misc->marketing(1, 'banner', 'keyword', 'website', 1, 1, 100, 100, $address, 100, 1)->bytes) === 861, 'Marketing size is wrong.');
|
expect(strlen($misc->marketing(1, 'banner', 'keyword', 'website', 1, 1, 100, 100, $address, 100, 1)->bytes) === 861, 'Marketing size is wrong.');
|
||||||
expect(strlen($misc->collateralClaim(str_repeat('01', 32), $address, 100, 1)->bytes) === 733, 'Collateral claim size is wrong.');
|
expect(strlen($misc->collateralClaim(str_repeat('01', 32), $address, 100, 1)->bytes) === 733, 'Collateral claim size is wrong.');
|
||||||
expect(strlen($misc->loanPayment(1000, str_repeat('01', 32), $address, 100, 100, 1)->bytes) === 781, 'Loan payment size is wrong.');
|
expect(strlen($misc->loanPayment(1000, str_repeat('01', 32), $address, 100, 100, 1)->bytes) === 781, 'Loan payment size is wrong.');
|
||||||
|
|
||||||
$agreements = new AgreementBuilder($crypto, $signer);
|
$agreements = new AgreementBuilder($crypto, $credentials);
|
||||||
$swap = $agreements->swap(1000, 'CLTC', 0, 100, 'TEST', 0, 200, $address, str_repeat('cd', 20) . '.cltc', 1, 2, 3, 4, 1);
|
$swap = $agreements->swap(1000, 'CLTC', 0, 100, 'TEST', 0, 200, $address, str_repeat('cd', 20) . '.cltc', 1, 2, 3, 4, 1);
|
||||||
$swap = $swap->signNext($crypto, $signer);
|
$swap = $swap->signNext($crypto, $credentials);
|
||||||
expect(strlen($swap->toSignedTransaction()->bytes) === 1471, 'Swap size is wrong.');
|
expect(strlen($swap->toSignedTransaction()->bytes) === 1471, 'Swap size is wrong.');
|
||||||
expect(DualSignedTransaction::import($swap->export())->isComplete(), 'Swap export/import lost signatures.');
|
expect(DualSignedTransaction::import($swap->export())->isComplete(), 'Swap export/import lost signatures.');
|
||||||
|
|
||||||
$loan = $agreements->loan('CLTC', 1000, $address, 'TEST', 2000, str_repeat('cd', 20) . '.cltc', 'm', 12, 100, 2, 300, 100, 1);
|
$loan = $agreements->loan('CLTC', 1000, $address, 'TEST', 2000, str_repeat('cd', 20) . '.cltc', 'm', 12, 100, 2, 300, 100, 1);
|
||||||
$loan = $loan->signNext($crypto, $signer);
|
$loan = $loan->signNext($crypto, $credentials);
|
||||||
expect(strlen($loan->toSignedTransaction()->bytes) === 1492, 'Loan size is wrong.');
|
expect(strlen($loan->toSignedTransaction()->bytes) === 1492, 'Loan size is wrong.');
|
||||||
|
|
||||||
$failedEndpoint = new TestTransport(
|
$failedEndpoint = new TestTransport(
|
||||||
|
|
@ -186,7 +176,7 @@ $endpointPool = new EndpointPoolTransport(
|
||||||
],
|
],
|
||||||
randomize: false,
|
randomize: false,
|
||||||
);
|
);
|
||||||
$pooledClient = new Client($endpointPool, $crypto, $handshakeProof);
|
$pooledClient = new Client($endpointPool, $crypto, $credentials);
|
||||||
expect(
|
expect(
|
||||||
$pooledClient->blockHeight() === 'pool reply',
|
$pooledClient->blockHeight() === 'pool reply',
|
||||||
'Endpoint pool did not return the successful fallback reply.',
|
'Endpoint pool did not return the successful fallback reply.',
|
||||||
|
|
@ -217,7 +207,7 @@ $protocolPool = new EndpointPoolTransport(
|
||||||
],
|
],
|
||||||
randomize: false,
|
randomize: false,
|
||||||
);
|
);
|
||||||
$protocolClient = new Client($protocolPool, $crypto, $handshakeProof);
|
$protocolClient = new Client($protocolPool, $crypto, $credentials);
|
||||||
$protocolRejected = false;
|
$protocolRejected = false;
|
||||||
try {
|
try {
|
||||||
$protocolClient->blockHeight();
|
$protocolClient->blockHeight();
|
||||||
|
|
@ -241,7 +231,7 @@ $allFailedPool = new EndpointPoolTransport(
|
||||||
],
|
],
|
||||||
randomize: false,
|
randomize: false,
|
||||||
);
|
);
|
||||||
$allFailedClient = new Client($allFailedPool, $crypto, $handshakeProof);
|
$allFailedClient = new Client($allFailedPool, $crypto, $credentials);
|
||||||
$allFailedMessage = '';
|
$allFailedMessage = '';
|
||||||
try {
|
try {
|
||||||
$allFailedClient->blockHeight();
|
$allFailedClient->blockHeight();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue