diff --git a/README.md b/README.md index fb75847..79881a1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The library exposes application-safe public RPC commands, including: - Marketing records - On-chain data storage - Governance proposals -- Transaction creation, signing, and broadcasting +- Transaction creation and broadcasting with application-provided signatures Miner relay commands, network mapping changes, monitor-state commands, node setup synchronization, and server-owner IP controls are intentionally not @@ -29,14 +29,13 @@ exposed. ## Requirements - PHP 8.1 or newer using a 64-bit build -- PHP GD -- PHP OpenSSL - The `skein` module from Contractless PHP Modules - The `oqsphp` module from Contractless PHP Modules Contractless uses the same Falcon key and signature sizes as `Falcon-padded-512`, but its FN-DSA signing mode requires a small message -adaptation. `NativeCrypto` performs that adaptation automatically. +adaptation. `NativeCrypto` performs that adaptation when verifying node +responses. The application that owns a wallet remains responsible for signing. Install and test the Contractless PHP Modules before installing this package: @@ -44,10 +43,10 @@ https://contractless.dev/contractless/Contractless-PHP-Modules ## Installation -Install version `0.3` through Packagist: +Install version `1.0` through Packagist: ```bash -composer require contractless/contractless-php-rpc:^0.3.0 -W +composer require contractless/contractless-php-rpc:^1.0.0 -W ``` Composer installs the package under `vendor/` and generates the required @@ -62,12 +61,12 @@ require __DIR__ . '/vendor/autoload.php'; use Contractless\Rpc\Client; use Contractless\Rpc\Crypto\NativeCrypto; +use Contractless\Rpc\Protocol\HandshakeProof; use Contractless\Rpc\Transport\StreamTransport; -use Contractless\Rpc\Wallet\Credentials; -$credentials = Credentials::fromWalletFile( - '/private/path/contractless.wallet', - 'wallet decryption key', +$handshake = HandshakeProof::fromHex( + $applicationPublicKeyHex, + $applicationHandshakeSignatureHex, ); $crypto = new NativeCrypto(); @@ -79,10 +78,14 @@ $client = new Client( timeout: 10.0, ), $crypto, - $credentials, + $handshake, ); ``` +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 mainnet RPC unless the node operator configured another port. @@ -124,7 +127,7 @@ $transport = new EndpointPoolTransport([ $client = new Client( $transport, $crypto, - $credentials, + $handshake, ); ``` @@ -155,7 +158,7 @@ $selectedTransport = $transport->transportFor($endpointId); $selectedClient = new Client( $selectedTransport, $crypto, - $credentials, + $handshake, ); ``` @@ -296,17 +299,15 @@ $result = $client->validateMessage( ); ``` -Register the wallet loaded into `$credentials`: +Wallet registration requires the application to provide the public key and +registration signature: ```php -$result = $client->registerOwnedWallet($address); -``` - -The lower-level registration method is available when an application has -already created the required signature: - -```php -$result = $client->registerWallet($address, $signatureHex); +$result = $client->registerWallet( + $address, + $publicKeyBytes, + $signatureHex, +); ``` ## Token, NFT, RWA, Swap, And Loan RPC @@ -395,10 +396,26 @@ including its voting and activation information. ## 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 use Contractless\Rpc\Transaction\TransferBuilder; -$transfer = (new TransferBuilder($crypto, $credentials))->create( +$transfer = (new TransferBuilder($crypto, $signer))->create( sender: $sender, receiver: $receiver, coin: 'CLTC', @@ -418,7 +435,7 @@ The same builder transfers base currency, tokens, NFTs, and RWAs. Set ```php use Contractless\Rpc\Transaction\AssetBuilder; -$assets = new AssetBuilder($crypto, $credentials); +$assets = new AssetBuilder($crypto, $signer); $createToken = $assets->createToken( creator: $address, @@ -471,7 +488,7 @@ signs the transaction: ```php use Contractless\Rpc\Transaction\AgreementBuilder; -$agreements = new AgreementBuilder($crypto, $credentials); +$agreements = new AgreementBuilder($crypto, $signer); $swap = $agreements->swap( expiration: time() + 3600, @@ -514,8 +531,7 @@ $exported = json_encode( ); ``` -The second party imports and signs it using their own `$crypto` and -`$credentials`: +The second party imports and signs it using its own signature provider: ```php use Contractless\Rpc\Transaction\DualSignedTransaction; @@ -524,7 +540,7 @@ $swap = DualSignedTransaction::import( json_decode($exported, true, flags: JSON_THROW_ON_ERROR), ); -$swap = $swap->signNext($crypto, $credentials); +$swap = $swap->signNext($crypto, $secondPartySigner); if ($swap->isComplete()) { $client->submitSignedTransaction($swap->toSignedTransaction()); @@ -536,7 +552,7 @@ if ($swap->isComplete()) { ```php use Contractless\Rpc\Transaction\MiscellaneousBuilder; -$miscellaneous = new MiscellaneousBuilder($crypto, $credentials); +$miscellaneous = new MiscellaneousBuilder($crypto, $signer); $payment = $miscellaneous->loanPayment( amount: 100_000_000, @@ -579,7 +595,7 @@ $client->submitSignedTransaction($marketing); ```php use Contractless\Rpc\Transaction\StorageBuilder; -$storage = new StorageBuilder($crypto, $credentials); +$storage = new StorageBuilder($crypto, $signer); $createKey = $storage->createKey( address: $address, @@ -643,7 +659,7 @@ of the preceding string transaction. ```php use Contractless\Rpc\Transaction\GovernanceBuilder; -$governance = new GovernanceBuilder($crypto, $credentials); +$governance = new GovernanceBuilder($crypto, $signer); $proposal = $governance->proposal( proposalHash: $proposalDocumentHash, @@ -687,42 +703,23 @@ $unsignedSigningJson = $transaction->unsignedJson; Use `submitSignedTransaction()` to broadcast the exact bytes without converting them to hexadecimal first. -## Wallet Files +## Wallet Custody -Load a normal Contractless wallet: +This package does not read Contractless wallet files, decode encrypted private +key images, accept wallet decryption keys, or store private keys. -```php -$credentials = Credentials::fromWalletFile( - '/private/path/contractless.wallet', - 'wallet decryption key', -); -``` +Applications are responsible for: -The loader: +- Loading or obtaining their wallet material +- 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 -- Decodes every supported Contractless private-key image orientation -- Verifies the encrypted payload HMAC -- Decrypts the Falcon private key -- 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); -``` +A faucet may keep its wallet loader in the faucet application. A browser wallet +may sign locally in the browser. A hosted application may use a hardware or +remote signer. All three can use the same RPC package because the connection +layer does not control wallet custody. ## Direct Public Command Calls @@ -753,7 +750,7 @@ validate and encode the command payload correctly. ## Error Handling -RPC transport, protocol, validation, and wallet failures throw exceptions: +RPC transport, protocol, and validation failures throw exceptions: ```php try { @@ -763,8 +760,7 @@ try { } ``` -Do not expose wallet errors, filesystem paths, private material, or internal -node replies directly to public users. +Do not expose internal node replies directly to public users. ## Testing diff --git a/composer.json b/composer.json index ecebdd7..c5cdc4c 100644 --- a/composer.json +++ b/composer.json @@ -5,9 +5,7 @@ "license": "MIT", "require": { "php": ">=8.1", - "ext-gd": "*", "ext-json": "*", - "ext-openssl": "*", "ext-oqsphp": "*", "ext-skein": "*" }, diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..ec78b9b --- /dev/null +++ b/composer.lock @@ -0,0 +1,23 @@ +{ + "_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" +} diff --git a/src/Client.php b/src/Client.php index 21ab625..f768918 100644 --- a/src/Client.php +++ b/src/Client.php @@ -9,9 +9,9 @@ use Contractless\Rpc\Exception\ProtocolException; use Contractless\Rpc\Protocol\Address; use Contractless\Rpc\Protocol\Binary; use Contractless\Rpc\Protocol\Command; +use Contractless\Rpc\Protocol\HandshakeProof; use Contractless\Rpc\Protocol\RequestBuilder; use Contractless\Rpc\Transport\TransportInterface; -use Contractless\Rpc\Wallet\Credentials; final class Client { @@ -26,7 +26,7 @@ final class Client public function __construct( private readonly TransportInterface $transport, private readonly CryptoInterface $crypto, - private readonly Credentials $credentials, + private readonly HandshakeProof $handshakeProof, ) { } @@ -175,29 +175,21 @@ final class Client return $this->call(Command::CONTRACT_BY_ADDRESS, Address::toBytes($address)); } - public function registerWallet(string $address, string $signature): string + public function registerWallet( + string $address, + string $publicKey, + string $signature, + ): string { + Binary::length($publicKey, 897, 'Falcon public key'); return $this->call( Command::REGISTER_WALLET, Address::toBytes($address) - . $this->credentials->publicKey + . $publicKey . 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 { return $this->call(Command::VANITY_LOOKUP, Address::toBytes($address)); @@ -291,16 +283,9 @@ final class Client private function buildHandshake(): string { - $digest = $this->crypto->skein256('aced'); - $signature = $this->crypto->sign( - $digest, - $this->credentials->privateKey, - $this->credentials->publicKey, - ); - return self::CLIENT_MARKER - . $signature - . $this->credentials->publicKey + . $this->handshakeProof->signature + . $this->handshakeProof->publicKey . Binary::u32(time()) . str_repeat("\0", 18); } diff --git a/src/Crypto/CallbackCrypto.php b/src/Crypto/CallbackCrypto.php index 385c95b..ab25362 100644 --- a/src/Crypto/CallbackCrypto.php +++ b/src/Crypto/CallbackCrypto.php @@ -11,7 +11,6 @@ final class CallbackCrypto implements CryptoInterface { public function __construct( private readonly Closure $skein, - private readonly Closure $signer, private readonly Closure $verifier, ) { } @@ -25,15 +24,6 @@ final class CallbackCrypto implements CryptoInterface 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 { return (bool) ($this->verifier)($message, $signature, $publicKey); diff --git a/src/Crypto/CryptoInterface.php b/src/Crypto/CryptoInterface.php index 1933ac1..bbbb3aa 100644 --- a/src/Crypto/CryptoInterface.php +++ b/src/Crypto/CryptoInterface.php @@ -9,9 +9,6 @@ interface CryptoInterface /** Return the raw 32-byte Skein-256 digest. */ 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. */ public function verify( string $message, diff --git a/src/Crypto/NativeCrypto.php b/src/Crypto/NativeCrypto.php index c1a2b2d..f8d998e 100644 --- a/src/Crypto/NativeCrypto.php +++ b/src/Crypto/NativeCrypto.php @@ -40,19 +40,6 @@ final class NativeCrypto implements CryptoInterface 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 { Binary::length($signature, 666, 'Falcon signature'); diff --git a/src/Protocol/HandshakeProof.php b/src/Protocol/HandshakeProof.php new file mode 100644 index 0000000..5342e63 --- /dev/null +++ b/src/Protocol/HandshakeProof.php @@ -0,0 +1,24 @@ +signer)($digest); + if (!is_string($signature) || strlen($signature) !== 666) { + throw new ProtocolException( + 'The signing callback must return exactly 666 raw bytes.', + ); + } + return $signature; + } +} diff --git a/src/Signing/SignatureProviderInterface.php b/src/Signing/SignatureProviderInterface.php new file mode 100644 index 0000000..5ee509d --- /dev/null +++ b/src/Signing/SignatureProviderInterface.php @@ -0,0 +1,11 @@ +signNext($this->crypto, $this->credentials); + ->signNext($this->crypto, $this->signer); } public function loan( @@ -96,7 +96,7 @@ final class AgreementBuilder . Binary::u8($gracePeriod) . Binary::u64($maxLateValue) . Binary::u64($fee); return (new DualSignedTransaction($json, $bytes, $hash, storesHash: true)) - ->signNext($this->crypto, $this->credentials); + ->signNext($this->crypto, $this->signer); } private function json(array $unsigned): string diff --git a/src/Transaction/AssetBuilder.php b/src/Transaction/AssetBuilder.php index 7fb5ac4..a539684 100644 --- a/src/Transaction/AssetBuilder.php +++ b/src/Transaction/AssetBuilder.php @@ -7,16 +7,19 @@ namespace Contractless\Rpc\Transaction; use Contractless\Rpc\Crypto\CryptoInterface; use Contractless\Rpc\Protocol\Address; use Contractless\Rpc\Protocol\Binary; -use Contractless\Rpc\Wallet\Credentials; +use Contractless\Rpc\Signing\SignatureProviderInterface; final class AssetBuilder { use SignsTransactions; - public function __construct(CryptoInterface $crypto, Credentials $credentials) + public function __construct( + CryptoInterface $crypto, + SignatureProviderInterface $signer, + ) { $this->crypto = $crypto; - $this->credentials = $credentials; + $this->signer = $signer; } public function createToken( diff --git a/src/Transaction/DualSignedTransaction.php b/src/Transaction/DualSignedTransaction.php index fb4d36f..0326c15 100644 --- a/src/Transaction/DualSignedTransaction.php +++ b/src/Transaction/DualSignedTransaction.php @@ -6,7 +6,7 @@ namespace Contractless\Rpc\Transaction; use Contractless\Rpc\Crypto\CryptoInterface; use Contractless\Rpc\Exception\ProtocolException; -use Contractless\Rpc\Wallet\Credentials; +use Contractless\Rpc\Signing\SignatureProviderInterface; final class DualSignedTransaction { @@ -20,7 +20,10 @@ final class DualSignedTransaction ) { } - public function signNext(CryptoInterface $crypto, Credentials $credentials): self + public function signNext( + CryptoInterface $crypto, + SignatureProviderInterface $signer, + ): self { $recalculatedHash = $crypto->skein256($this->unsignedJson); if (!hash_equals($this->hash, $recalculatedHash)) { @@ -28,7 +31,7 @@ final class DualSignedTransaction 'The exported transaction hash does not match its unsigned transaction.', ); } - $signature = $crypto->sign($this->hash, $credentials->privateKey, $credentials->publicKey); + $signature = $signer->sign($this->hash); if ($this->signature1 === '') { return new self( $this->unsignedJson, diff --git a/src/Transaction/GovernanceBuilder.php b/src/Transaction/GovernanceBuilder.php index 12d3813..6737866 100644 --- a/src/Transaction/GovernanceBuilder.php +++ b/src/Transaction/GovernanceBuilder.php @@ -8,16 +8,19 @@ use Contractless\Rpc\Crypto\CryptoInterface; use Contractless\Rpc\Protocol\Address; use Contractless\Rpc\Protocol\Binary; use Contractless\Rpc\Protocol\RequestBuilder; -use Contractless\Rpc\Wallet\Credentials; +use Contractless\Rpc\Signing\SignatureProviderInterface; final class GovernanceBuilder { use SignsTransactions; - public function __construct(CryptoInterface $crypto, Credentials $credentials) + public function __construct( + CryptoInterface $crypto, + SignatureProviderInterface $signer, + ) { $this->crypto = $crypto; - $this->credentials = $credentials; + $this->signer = $signer; } public function proposal( diff --git a/src/Transaction/MiscellaneousBuilder.php b/src/Transaction/MiscellaneousBuilder.php index 584fc54..c140a47 100644 --- a/src/Transaction/MiscellaneousBuilder.php +++ b/src/Transaction/MiscellaneousBuilder.php @@ -8,16 +8,19 @@ use Contractless\Rpc\Crypto\CryptoInterface; use Contractless\Rpc\Protocol\Address; use Contractless\Rpc\Protocol\Binary; use Contractless\Rpc\Protocol\RequestBuilder; -use Contractless\Rpc\Wallet\Credentials; +use Contractless\Rpc\Signing\SignatureProviderInterface; final class MiscellaneousBuilder { use SignsTransactions; - public function __construct(CryptoInterface $crypto, Credentials $credentials) + public function __construct( + CryptoInterface $crypto, + SignatureProviderInterface $signer, + ) { $this->crypto = $crypto; - $this->credentials = $credentials; + $this->signer = $signer; } public function marketing( @@ -88,11 +91,7 @@ final class MiscellaneousBuilder JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION, ); $hash = $this->crypto->skein256($json); - $signature = $this->crypto->sign( - $hash, - $this->credentials->privateKey, - $this->credentials->publicKey, - ); + $signature = $this->signer->sign($hash); $binary = Binary::u8(8) . Binary::u32($timestamp) . Binary::u64($amount) . RequestBuilder::hash($contractHash, 'Loan contract hash') . Address::toBytes($address) . Binary::u64($tip) . Binary::u64($fee) diff --git a/src/Transaction/SignsTransactions.php b/src/Transaction/SignsTransactions.php index 45983ac..68f8b8e 100644 --- a/src/Transaction/SignsTransactions.php +++ b/src/Transaction/SignsTransactions.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace Contractless\Rpc\Transaction; use Contractless\Rpc\Crypto\CryptoInterface; -use Contractless\Rpc\Wallet\Credentials; +use Contractless\Rpc\Signing\SignatureProviderInterface; trait SignsTransactions { private CryptoInterface $crypto; - private Credentials $credentials; + private SignatureProviderInterface $signer; private function sign(array $unsigned, string $binary): SignedTransaction { @@ -19,11 +19,7 @@ trait SignsTransactions JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION, ); $digest = $this->crypto->skein256($json); - $signature = $this->crypto->sign( - $digest, - $this->credentials->privateKey, - $this->credentials->publicKey, - ); + $signature = $this->signer->sign($digest); return new SignedTransaction($binary . $signature, $signature, $json); } } diff --git a/src/Transaction/StorageBuilder.php b/src/Transaction/StorageBuilder.php index 5e596e6..15f0833 100644 --- a/src/Transaction/StorageBuilder.php +++ b/src/Transaction/StorageBuilder.php @@ -9,16 +9,19 @@ use Contractless\Rpc\Exception\ProtocolException; use Contractless\Rpc\Protocol\Address; use Contractless\Rpc\Protocol\Binary; use Contractless\Rpc\Protocol\RequestBuilder; -use Contractless\Rpc\Wallet\Credentials; +use Contractless\Rpc\Signing\SignatureProviderInterface; final class StorageBuilder { use SignsTransactions; - public function __construct(CryptoInterface $crypto, Credentials $credentials) + public function __construct( + CryptoInterface $crypto, + SignatureProviderInterface $signer, + ) { $this->crypto = $crypto; - $this->credentials = $credentials; + $this->signer = $signer; } public function createKey( @@ -206,11 +209,7 @@ final class StorageBuilder . ',"address":' . json_encode($address, JSON_THROW_ON_ERROR) . ',"txfee":' . $fee . '}'; $digest = $this->crypto->skein256($json); - $signature = $this->crypto->sign( - $digest, - $this->credentials->privateKey, - $this->credentials->publicKey, - ); + $signature = $this->signer->sign($digest); $binary = Binary::u8($type) . Binary::u32($timestamp) . RequestBuilder::hash($storageKey, 'Storage key') . $key . $valueBytes . Address::toBytes($address) . Binary::u64($fee); diff --git a/src/Transaction/TransferBuilder.php b/src/Transaction/TransferBuilder.php index f12d2a5..1e48c8b 100644 --- a/src/Transaction/TransferBuilder.php +++ b/src/Transaction/TransferBuilder.php @@ -7,13 +7,13 @@ namespace Contractless\Rpc\Transaction; use Contractless\Rpc\Crypto\CryptoInterface; use Contractless\Rpc\Protocol\Address; use Contractless\Rpc\Protocol\Binary; -use Contractless\Rpc\Wallet\Credentials; +use Contractless\Rpc\Signing\SignatureProviderInterface; final class TransferBuilder { public function __construct( private readonly CryptoInterface $crypto, - private readonly Credentials $credentials, + private readonly SignatureProviderInterface $signer, ) { } @@ -42,11 +42,7 @@ final class TransferBuilder ], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES); $digest = $this->crypto->skein256($unsigned); - $signature = $this->crypto->sign( - $digest, - $this->credentials->privateKey, - $this->credentials->publicKey, - ); + $signature = $this->signer->sign($digest); $bytes = Binary::u8(2) . Binary::u32($timestamp) diff --git a/tests/run.php b/tests/run.php index c5bb1be..8550cd4 100644 --- a/tests/run.php +++ b/tests/run.php @@ -21,6 +21,8 @@ use Contractless\Rpc\Exception\ProtocolException; use Contractless\Rpc\Exception\TransportException; use Contractless\Rpc\Protocol\Address; use Contractless\Rpc\Protocol\Command; +use Contractless\Rpc\Protocol\HandshakeProof; +use Contractless\Rpc\Signing\CallbackSignatureProvider; use Contractless\Rpc\Transport\EndpointPoolTransport; use Contractless\Rpc\Transport\TransportInterface; use Contractless\Rpc\Transaction\TransferBuilder; @@ -30,7 +32,6 @@ use Contractless\Rpc\Transaction\StorageBuilder; use Contractless\Rpc\Transaction\MiscellaneousBuilder; use Contractless\Rpc\Transaction\AgreementBuilder; use Contractless\Rpc\Transaction\DualSignedTransaction; -use Contractless\Rpc\Wallet\Credentials; function expect(bool $condition, string $message): void { @@ -87,11 +88,16 @@ $transport = new class implements TransportInterface { $crypto = new CallbackCrypto( 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, ); -$credentials = new Credentials(str_repeat("\x33", 897), str_repeat("\x44", 1281)); -$client = new Client($transport, $crypto, $credentials); +$signer = new CallbackSignatureProvider( + fn (string $digest): string => str_repeat("\x22", 666), +); +$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(strlen($transport->handshake) === 1587, 'Handshake request has the wrong size.'); @@ -103,11 +109,15 @@ $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(strlen($transport->request) === 4 + 32 + 50 + 22, 'Storage cost payload has the wrong size.'); -$client->registerOwnedWallet(str_repeat('ab', 20) . '.cltc'); +$client->registerWallet( + 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(strlen($transport->request) === 4 + 22 + 897 + 666, 'Wallet registration payload has the wrong size.'); -$transfer = (new TransferBuilder($crypto, $credentials))->create( +$transfer = (new TransferBuilder($crypto, $signer))->create( str_repeat('ab', 20) . '.cltc', str_repeat('cd', 20) . '.cltc', 'CLTC', @@ -127,19 +137,19 @@ expect( ); $address = str_repeat('ab', 20) . '.cltc'; -$assets = new AssetBuilder($crypto, $credentials); +$assets = new AssetBuilder($crypto, $signer); 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->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->vanity($address, str_repeat('a', 20) . '.cltc', 100, 1)->bytes) === 723, 'Vanity size is wrong.'); -$governance = new GovernanceBuilder($crypto, $credentials); +$governance = new GovernanceBuilder($crypto, $signer); 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->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, $credentials); +$storage = new StorageBuilder($crypto, $signer); 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->u32(str_repeat('01', 32), 'count', 42, $address, 100, 1)->bytes) === 787, 'Storage u32 size is wrong.'); @@ -150,19 +160,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->delete(str_repeat('01', 32), 'bio', $address, 100, 1)->bytes) === 783, 'Storage delete size is wrong.'); -$misc = new MiscellaneousBuilder($crypto, $credentials); +$misc = new MiscellaneousBuilder($crypto, $signer); 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->loanPayment(1000, str_repeat('01', 32), $address, 100, 100, 1)->bytes) === 781, 'Loan payment size is wrong.'); -$agreements = new AgreementBuilder($crypto, $credentials); +$agreements = new AgreementBuilder($crypto, $signer); $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, $credentials); +$swap = $swap->signNext($crypto, $signer); expect(strlen($swap->toSignedTransaction()->bytes) === 1471, 'Swap size is wrong.'); 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 = $loan->signNext($crypto, $credentials); +$loan = $loan->signNext($crypto, $signer); expect(strlen($loan->toSignedTransaction()->bytes) === 1492, 'Loan size is wrong.'); $failedEndpoint = new TestTransport( @@ -176,7 +186,7 @@ $endpointPool = new EndpointPoolTransport( ], randomize: false, ); -$pooledClient = new Client($endpointPool, $crypto, $credentials); +$pooledClient = new Client($endpointPool, $crypto, $handshakeProof); expect( $pooledClient->blockHeight() === 'pool reply', 'Endpoint pool did not return the successful fallback reply.', @@ -207,7 +217,7 @@ $protocolPool = new EndpointPoolTransport( ], randomize: false, ); -$protocolClient = new Client($protocolPool, $crypto, $credentials); +$protocolClient = new Client($protocolPool, $crypto, $handshakeProof); $protocolRejected = false; try { $protocolClient->blockHeight(); @@ -231,7 +241,7 @@ $allFailedPool = new EndpointPoolTransport( ], randomize: false, ); -$allFailedClient = new Client($allFailedPool, $crypto, $credentials); +$allFailedClient = new Client($allFailedPool, $crypto, $handshakeProof); $allFailedMessage = ''; try { $allFailedClient->blockHeight();