From 441c1e569ab7d861ae7ce3627e9b3410a1b3312b Mon Sep 17 00:00:00 2001 From: Hergen Dillema Date: Sun, 8 Jan 2023 20:40:12 +0100 Subject: [PATCH 1/7] Complete refactor --- readme.md | 2 +- src/Client.php | 19 +- src/Contracts/{Pact.php => Client.php} | 10 +- src/Contracts/Collection.php | 10 + .../Contracts => Contracts/Crypto}/Hash.php | 2 +- src/Contracts/Crypto/KeyFactory.php | 10 + .../Crypto/MessageSigner.php} | 11 +- src/Contracts/Pact/CommandFactory.php | 23 +++ src/Crypto/CommandSigner.php | 41 ++++ src/Crypto/Contracts/KeyPair.php | 8 - src/Crypto/Hash.php | 2 +- src/Crypto/KeyFactory.php | 29 +++ src/Crypto/KeyPair.php | 35 ---- src/Crypto/KeyPairCollection.php | 24 --- src/Crypto/{Signer.php => MessageSigner.php} | 29 +-- src/Crypto/SignatureCollection.php | 41 ---- src/DataMappers/CommandMapper.php | 192 ++++++++++++++++++ .../SignedCommandCollectionMapper.php | 30 +++ src/DataMappers/SignedCommandMapper.php | 64 ++++++ src/Exceptions/MissingPayloadException.php | 9 + src/Exceptions/UnknownMetadataException.php | 9 + src/Pact/Command.php | 172 ---------------- src/Pact/CommandFactory.php | 136 +++++++++++++ src/Pact/Meta.php | 44 ---- src/Pact/MetadataFactory.php | 47 +++++ src/Pact/RequestKeyCollection.php | 36 ---- src/Pact/SignedCommand.php | 71 ------- src/Pact/SignedCommandCollection.php | 51 ----- src/Traits/HasCollectionMethods.php | 28 +++ src/ValueObjects/Command/Command.php | 18 ++ src/ValueObjects/Command/Metadata.php | 18 ++ .../Command/Payload}/ContinuePayload.php | 13 +- .../Command/Payload}/ExecutePayload.php | 10 +- .../Command/Payload}/Payload.php | 11 +- .../Command/Payload}/PayloadType.php | 2 +- src/ValueObjects/Command/SignedCommand.php | 26 +++ .../Command/SignedCommandCollection.php | 16 ++ .../RequestKey}/RequestKey.php | 2 +- .../RequestKey/RequestKeyCollection.php | 23 +++ src/ValueObjects/Signer/Capability.php | 12 ++ .../Signer/CapabilityCollection.php | 16 ++ src/ValueObjects/Signer/KeyPair.php | 12 ++ src/ValueObjects/Signer/KeyPairCollection.php | 16 ++ src/ValueObjects/Signer/PublicKey.php | 24 +++ src/ValueObjects/Signer/SecretKey.php | 13 ++ .../Signer}/Signature.php | 2 +- .../Signer/SignatureCollection.php | 16 ++ src/ValueObjects/Signer/Signer.php | 12 ++ src/ValueObjects/Signer/SignerCollection.php | 16 ++ tests/Unit/ClientTest.php | 24 +-- tests/Unit/Crypto/KeyPairCollectionTest.php | 4 +- tests/Unit/Crypto/KeyPairTest.php | 2 +- tests/Unit/Crypto/SignatureCollectionTest.php | 4 +- tests/Unit/Crypto/SignerTest.php | 34 ++-- tests/Unit/Pact/CommandTest.php | 32 +-- tests/Unit/Pact/ContinuePayloadTest.php | 2 +- tests/Unit/Pact/ExecutePayloadTest.php | 2 +- tests/Unit/Pact/MetaTest.php | 12 +- tests/Unit/Pact/PayloadTest.php | 8 +- tests/Unit/Pact/RequestKeyCollectionTest.php | 4 +- .../Unit/Pact/SignedCommandCollectionTest.php | 24 +-- tests/Unit/Pact/SignedCommandTest.php | 24 +-- 62 files changed, 989 insertions(+), 650 deletions(-) rename src/Contracts/{Pact.php => Client.php} (69%) create mode 100644 src/Contracts/Collection.php rename src/{Crypto/Contracts => Contracts/Crypto}/Hash.php (76%) create mode 100644 src/Contracts/Crypto/KeyFactory.php rename src/{Crypto/Contracts/Signer.php => Contracts/Crypto/MessageSigner.php} (62%) create mode 100644 src/Contracts/Pact/CommandFactory.php create mode 100644 src/Crypto/CommandSigner.php delete mode 100644 src/Crypto/Contracts/KeyPair.php create mode 100644 src/Crypto/KeyFactory.php delete mode 100644 src/Crypto/KeyPair.php delete mode 100644 src/Crypto/KeyPairCollection.php rename src/Crypto/{Signer.php => MessageSigner.php} (60%) delete mode 100644 src/Crypto/SignatureCollection.php create mode 100644 src/DataMappers/CommandMapper.php create mode 100644 src/DataMappers/SignedCommandCollectionMapper.php create mode 100644 src/DataMappers/SignedCommandMapper.php create mode 100644 src/Exceptions/MissingPayloadException.php create mode 100644 src/Exceptions/UnknownMetadataException.php delete mode 100644 src/Pact/Command.php create mode 100644 src/Pact/CommandFactory.php delete mode 100644 src/Pact/Meta.php create mode 100644 src/Pact/MetadataFactory.php delete mode 100644 src/Pact/RequestKeyCollection.php delete mode 100644 src/Pact/SignedCommand.php delete mode 100644 src/Pact/SignedCommandCollection.php create mode 100644 src/Traits/HasCollectionMethods.php create mode 100644 src/ValueObjects/Command/Command.php create mode 100644 src/ValueObjects/Command/Metadata.php rename src/{Pact => ValueObjects/Command/Payload}/ContinuePayload.php (52%) rename src/{Pact => ValueObjects/Command/Payload}/ExecutePayload.php (52%) rename src/{Pact => ValueObjects/Command/Payload}/Payload.php (79%) rename src/{Pact => ValueObjects/Command/Payload}/PayloadType.php (71%) create mode 100644 src/ValueObjects/Command/SignedCommand.php create mode 100644 src/ValueObjects/Command/SignedCommandCollection.php rename src/{Pact => ValueObjects/RequestKey}/RequestKey.php (77%) create mode 100644 src/ValueObjects/RequestKey/RequestKeyCollection.php create mode 100644 src/ValueObjects/Signer/Capability.php create mode 100644 src/ValueObjects/Signer/CapabilityCollection.php create mode 100644 src/ValueObjects/Signer/KeyPair.php create mode 100644 src/ValueObjects/Signer/KeyPairCollection.php create mode 100644 src/ValueObjects/Signer/PublicKey.php create mode 100644 src/ValueObjects/Signer/SecretKey.php rename src/{Crypto => ValueObjects/Signer}/Signature.php (86%) create mode 100644 src/ValueObjects/Signer/SignatureCollection.php create mode 100644 src/ValueObjects/Signer/Signer.php create mode 100644 src/ValueObjects/Signer/SignerCollection.php diff --git a/readme.md b/readme.md index 6216bd9..c0393a6 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ composer require kadena-php/client Key Pairs are used to sign your Pact commands. You can generate a new KeyPair using ```php -$keyPair = \Kadena\Crypto\KeyPair::generate(); +$keyPair = \Kadena\ValueObjects\Signer\KeyPair::generate(); ``` ### Commands Commands are requests sent to the Pact API. diff --git a/src/Client.php b/src/Client.php index 2447143..6c447c7 100644 --- a/src/Client.php +++ b/src/Client.php @@ -3,11 +3,13 @@ namespace Kadena; use JsonException; -use Kadena\Contracts\Pact as PactContract; -use Kadena\Pact\RequestKey; -use Kadena\Pact\RequestKeyCollection; -use Kadena\Pact\SignedCommand; -use Kadena\Pact\SignedCommandCollection; +use Kadena\Contracts\Client as ClientContract; +use Kadena\DataMappers\SignedCommandCollectionMapper; +use Kadena\DataMappers\SignedCommandMapper; +use Kadena\ValueObjects\Command\SignedCommand; +use Kadena\ValueObjects\Command\SignedCommandCollection; +use Kadena\ValueObjects\RequestKey\RequestKey; +use Kadena\ValueObjects\RequestKey\RequestKeyCollection; use Symfony\Component\HttpClient\HttpClient; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; @@ -17,7 +19,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; -class Client implements PactContract +class Client implements ClientContract { private HttpClientInterface $client; @@ -36,11 +38,12 @@ public function __construct(private readonly string $apiUrl, HttpClientInterface * @throws RedirectionExceptionInterface * @throws DecodingExceptionInterface * @throws ClientExceptionInterface + * @throws JsonException */ public function send(SignedCommandCollection $commands): RequestKeyCollection { $response = $this->client->request('POST', $this->apiUrl . '/api/v1/send', [ - 'json' => $commands->toPayload(), + 'json' => SignedCommandCollectionMapper::toArray($commands), ]); $requestKeys = array_map(static function (string $requestKey) { @@ -57,7 +60,7 @@ public function send(SignedCommandCollection $commands): RequestKeyCollection public function local(SignedCommand $command): ResponseInterface { return $this->client->request('POST', $this->apiUrl . '/api/v1/local', [ - 'json' => $command->toArray(), + 'json' => SignedCommandMapper::toArray($command), ]); } diff --git a/src/Contracts/Pact.php b/src/Contracts/Client.php similarity index 69% rename from src/Contracts/Pact.php rename to src/Contracts/Client.php index 8738a5f..7dc7f98 100644 --- a/src/Contracts/Pact.php +++ b/src/Contracts/Client.php @@ -2,13 +2,13 @@ namespace Kadena\Contracts; -use Kadena\Pact\RequestKey; -use Kadena\Pact\RequestKeyCollection; -use Kadena\Pact\SignedCommand; -use Kadena\Pact\SignedCommandCollection; +use Kadena\ValueObjects\Command\SignedCommand; +use Kadena\ValueObjects\Command\SignedCommandCollection; +use Kadena\ValueObjects\RequestKey\RequestKey; +use Kadena\ValueObjects\RequestKey\RequestKeyCollection; use Symfony\Contracts\HttpClient\ResponseInterface; -interface Pact +interface Client { public function send(SignedCommandCollection $commands): RequestKeyCollection; diff --git a/src/Contracts/Collection.php b/src/Contracts/Collection.php new file mode 100644 index 0000000..df84e76 --- /dev/null +++ b/src/Contracts/Collection.php @@ -0,0 +1,10 @@ +toArray() as $keyPair) { + $signatures[] = MessageSigner::sign($commandString, $keyPair); + } + + return new SignedCommand( + hash: $commandHash, + signatures: new SignatureCollection(...$signatures), + command: $command + ); + } +} diff --git a/src/Crypto/Contracts/KeyPair.php b/src/Crypto/Contracts/KeyPair.php deleted file mode 100644 index 26eb7ad..0000000 --- a/src/Crypto/Contracts/KeyPair.php +++ /dev/null @@ -1,8 +0,0 @@ -getPublicKey()), + secretKey: new SecretKey($keyPair->getSecretKey()) + ); + } +} diff --git a/src/Crypto/KeyPair.php b/src/Crypto/KeyPair.php deleted file mode 100644 index 6c1745b..0000000 --- a/src/Crypto/KeyPair.php +++ /dev/null @@ -1,35 +0,0 @@ -getPublicKey(), - secretKey: $keyPair->getSecretKey() - ); - } -} diff --git a/src/Crypto/KeyPairCollection.php b/src/Crypto/KeyPairCollection.php deleted file mode 100644 index 44bb5a7..0000000 --- a/src/Crypto/KeyPairCollection.php +++ /dev/null @@ -1,24 +0,0 @@ -array = $keyPair; - } - - /** - * @return KeyPair[] - */ - public function toArray(): array - { - return $this->array; - } -} diff --git a/src/Crypto/Signer.php b/src/Crypto/MessageSigner.php similarity index 60% rename from src/Crypto/Signer.php rename to src/Crypto/MessageSigner.php index ba02391..22e7874 100644 --- a/src/Crypto/Signer.php +++ b/src/Crypto/MessageSigner.php @@ -2,10 +2,10 @@ namespace Kadena\Crypto; -use Kadena\Crypto\Contracts\Signer as SignerContract; -use Kadena\Pact\Command; +use Kadena\Contracts\Crypto\MessageSigner as MessageSignerContract; +use Kadena\ValueObjects\Signer\KeyPair; +use Kadena\ValueObjects\Signer\Signature; use ParagonIE\ConstantTime\Base64UrlSafe; -use ParagonIE\ConstantTime\Hex; use ParagonIE\Halite\Alerts\InvalidSignature; use ParagonIE\Halite\Alerts\InvalidType; use ParagonIE\Halite\Asymmetric\Crypto; @@ -13,11 +13,9 @@ use ParagonIE\Halite\Halite; use SodiumException; -final class Signer implements SignerContract +final class MessageSigner implements MessageSignerContract { /** - * Sign a message given a key pair and return a SignedMessage object - * * @throws InvalidType * @throws SodiumException */ @@ -29,25 +27,21 @@ public static function sign(string $message, KeyPair $keyPair): Signature } /** - * Sign a hash given a key pair and return a SignedMessage object - * * @throws InvalidType * @throws SodiumException */ public static function signHash(string $hash, KeyPair $keyPair): Signature { - $signature = Crypto::sign($hash, $keyPair->secretKey, Halite::ENCODE_HEX); + $signature = Crypto::sign($hash, $keyPair->secretKey->key, Halite::ENCODE_HEX); return new Signature( hash: Base64UrlSafe::encodeUnpadded($hash), signature: $signature, - publicKey: Hex::encode($keyPair->publicKey->getRawKeyMaterial()) + publicKey: $keyPair->publicKey->toString() ); } /** - * Verify a signature given a public key and message - * * @throws InvalidType * @throws InvalidSignature * @throws SodiumException @@ -56,15 +50,4 @@ public static function verifySignature(string $message, string $signature, Signa { return Crypto::verify($message, $publicKey, $signature, Halite::ENCODE_HEX); } - - /** - * @throws InvalidType - * @throws SodiumException - */ - public static function signCommand(Command $command, KeyPair $keyPair): Signature - { - $message = $command->toString(); - - return self::sign($message, $keyPair); - } } diff --git a/src/Crypto/SignatureCollection.php b/src/Crypto/SignatureCollection.php deleted file mode 100644 index 46a5446..0000000 --- a/src/Crypto/SignatureCollection.php +++ /dev/null @@ -1,41 +0,0 @@ -array = $signature; - } - - public function first(): Signature - { - return $this->array[0]; - } - - /** - * @return Signature[] - */ - public function toArray(): array - { - return $this->array; - } - - public static function fromArray(array $signatures, string $hash): self - { - return new self(...array_map(static function (array|object $signature) use ($hash) { - $signature = (array) $signature; - - return new Signature( - hash: $hash, - signature: $signature['sig'] - ); - }, $signatures)); - } -} diff --git a/src/DataMappers/CommandMapper.php b/src/DataMappers/CommandMapper.php new file mode 100644 index 0000000..09472c6 --- /dev/null +++ b/src/DataMappers/CommandMapper.php @@ -0,0 +1,192 @@ +payload->payloadType === PayloadType::EXECUTE) { + $payload = [ + 'data' => $command->payload->executePayload->data, + 'code' => $command->payload->executePayload->code, + ]; + } else { + $payload = [ + 'data' => $command->payload->continuePayload->data, + 'step' => $command->payload->continuePayload->step, + 'rollback' => $command->payload->continuePayload->rollback, + 'pactId' => $command->payload->continuePayload->pactId, + 'proof' => $command->payload->continuePayload->proof, + ]; + } + + $array = [ + 'networkId' => $command->networkId, + 'payload' => [ + $command->payload->payloadType->value => $payload + ], + 'meta' => [ + 'creationTime' => $command->meta->creationTime->getTimestamp(), + 'ttl' => $command->meta->ttl, + 'gasLimit' => $command->meta->gasLimit, + 'chainId' => $command->meta->chainId, + 'gasPrice' => $command->meta->gasPrice, + 'sender' => $command->meta->sender, + ], + 'nonce' => $command->nonce + ]; + + if ($command->signers->count() === 0) { + return $array; + } + + $signers = []; + + /** @var Signer $signer */ + foreach ($command->signers->toArray() as $signer) { + $signerArray = [ + 'pubKey' => $signer->publicKey->toString(), + ]; + + if ($signer->capabilities->count() !== 0) { + $capabilityList = []; + + /** @var Capability $capability */ + foreach ($signer->capabilities->toArray() as $capability) { + $capabilityList[] = [ + 'name' => $capability->name, + 'args' => $capability->arguments, + ]; + } + + $signerArray['clist'] = $capabilityList; + } + + $signers[] = $signerArray; + } + + $array['signers'] = $signers; + + return $array; + } + + /** + * @throws JsonException + */ + public static function toString(Command $command): string + { + return json_encode(self::toArray($command), JSON_THROW_ON_ERROR); + } + + /** + * @throws JsonException + */ + public static function fromString(string $commandJson): Command + { + $command = json_decode($commandJson, false, 512, JSON_THROW_ON_ERROR); + + if (! isset($command->signers, $command->payload, $command->meta)) { + throw new InvalidArgumentException('Invalid Command JSON string given'); + } + + $meta = $command->meta; + + if (! isset( + $meta->creationTime, + $meta->ttl, + $meta->gasLimit, + $meta->chainId, + $meta->gasPrice, + $meta->sender, + )) { + throw new InvalidArgumentException('Invalid meta object given'); + } + + $payloadType = PayloadType::from(key((array) $command->payload)); + + if ($payloadType === PayloadType::EXECUTE) { + $exec = $command->payload->exec; + + if (! isset($exec->data, $exec->code)) { + throw new InvalidArgumentException('Invalid execute command object given'); + } + + $payload = new Payload( + payloadType: $payloadType, + executePayload: new ExecutePayload( + code: $exec->code, + data: (array) ($exec->data ?? []), + ) + ); + } elseif ($payloadType === PayloadType::CONTINUE) { + $cont = $command->payload->cont; + + if (! isset($cont->data, $cont->code, $cont->pactId, $cont->rollback, $cont->step)) { + throw new InvalidArgumentException('Invalid continue command object given'); + } + + $payload = new Payload( + payloadType: $payloadType, + continuePayload: new ContinuePayload( + pactId: $cont->pactId, + rollback: $cont->rollback, + step: $cont->step, + proof: $cont->proof ?? null, + data: (array) ($cont->data ?? []), + ) + ); + } else { + throw new InvalidArgumentException('Unknown payload type'); + } + + $signers = []; + + foreach ($command->signers as $signer) { + $capabilities = []; + + if (isset($signer->clist)) { + foreach ($signer->clist as $capabilityList) { + $capabilities[] = new Capability( + name: $capabilityList->name, + arguments: $capabilityList->args + ); + } + } + + $signers[] = new Signer( + publicKey: $signer->pubKey, + capabilities: new CapabilityCollection(...$capabilities) + ); + } + + return new Command( + meta: new Metadata( + creationTime: Carbon::createFromTimestamp($meta->creationTime), + ttl: $meta->ttl, + gasLimit: $meta->gasLimit, + chainId: $meta->chainId, + gasPrice: $meta->gasPrice, + sender: $meta->sender + ), + payload: $payload, + networkId: $command->networkId ?? '0', + nonce: $command->nonce ?? Carbon::now()->toISOString(), + signers: new SignerCollection(...$signers) + ); + } +} diff --git a/src/DataMappers/SignedCommandCollectionMapper.php b/src/DataMappers/SignedCommandCollectionMapper.php new file mode 100644 index 0000000..9d3e3a6 --- /dev/null +++ b/src/DataMappers/SignedCommandCollectionMapper.php @@ -0,0 +1,30 @@ + array_map(static function (SignedCommand $command){ + return SignedCommandMapper::toArray($command); + }, $commands->toArray()) + ]; + } + + /** + * @throws JsonException + */ + public static function toString(SignedCommandCollection $commands): string + { + return json_encode(self::toArray($commands), JSON_THROW_ON_ERROR); + } +} diff --git a/src/DataMappers/SignedCommandMapper.php b/src/DataMappers/SignedCommandMapper.php new file mode 100644 index 0000000..c467366 --- /dev/null +++ b/src/DataMappers/SignedCommandMapper.php @@ -0,0 +1,64 @@ + CommandMapper::toString($command->command), + 'hash' => $command->hash, + 'sigs' => array_map(static function (Signature $signature) { + return ['sig' => $signature->signature]; + }, $command->signatures->toArray()) + ]; + } + + /** + * @throws JsonException + */ + public static function toString(SignedCommand $command): string + { + return json_encode(self::toArray($command), JSON_THROW_ON_ERROR); + } + + /** + * @throws JsonException + */ + public static function fromString(string $commandJson): SignedCommand + { + $command = json_decode($commandJson, false, 512, JSON_THROW_ON_ERROR); + + if (! isset($command->hash, $command->sigs, $command->cmd)) { + throw new InvalidArgumentException('Invalid Signed Command JSON string given'); + } + + return new SignedCommand( + hash: $command->hash, + signatures: self::getSignatures((array) $command->sigs, $command->hash), + command: CommandMapper::fromString($command->cmd), + ); + } + + private static function getSignatures(array $signatures, string $hash): SignatureCollection + { + return new SignatureCollection(...array_map(static function (array|object $signature) use ($hash) { + $signature = (array) $signature; + + return new Signature( + hash: $hash, + signature: $signature['sig'] + ); + }, $signatures)); + } +} diff --git a/src/Exceptions/MissingPayloadException.php b/src/Exceptions/MissingPayloadException.php new file mode 100644 index 0000000..cf1e859 --- /dev/null +++ b/src/Exceptions/MissingPayloadException.php @@ -0,0 +1,9 @@ +nonce = Carbon::now()->toISOString(); - } else { - $this->nonce = $nonce; - } - } - - /** - * @throws JsonException - */ - public function toString(): string - { - return json_encode($this->toArray(), JSON_THROW_ON_ERROR); - } - - public function toArray(): array - { - return [ - 'signers' => $this->signers, - 'networkId' => $this->networkId, - 'payload' => $this->payload->toArray(), - 'meta' => $this->meta->toArray(), - 'nonce' => $this->nonce, - ]; - } - - /** - * @throws InvalidType - * @throws SodiumException - */ - public function getSignedCommand(KeyPairCollection $keyPairCollection): SignedCommand - { - $this->setSigners(array_map([$this, 'getSigners'], $keyPairCollection->toArray())); - - $signatures = []; - - foreach ($keyPairCollection->toArray() as $keyPair) { - $signatures[] = Signer::signCommand($this, $keyPair); - } - - $signatureCollection = new SignatureCollection(...$signatures); - - return new SignedCommand( - hash: $signatureCollection->first()->hash, - signatures: $signatureCollection, - command: $this - ); - } - - private function getSigners(KeyPair $keyPair): string - { - return Hex::encode($keyPair->publicKey->getRawKeyMaterial()); - } - - public function setSigners(array $signers): void - { - $pubKeys = []; - - foreach ($signers as $signer) { - $pubKeys[] = ['pubKey' => $signer]; - } - - $this->signers = $pubKeys; - } - - public static function fromString(string $commandJson): self - { - $command = json_decode($commandJson, false, 512, JSON_THROW_ON_ERROR); - - if (! isset($command->signers, $command->payload, $command->meta)) { - throw new InvalidArgumentException('Invalid Command JSON string given'); - } - - $meta = $command->meta; - - if (! isset( - $meta->creationTime, - $meta->ttl, - $meta->gasLimit, - $meta->chainId, - $meta->gasPrice, - $meta->sender, - )) { - throw new InvalidArgumentException('Invalid meta object given'); - } - - $payloadType = PayloadType::from(key((array) $command->payload)); - - if ($payloadType === PayloadType::EXECUTE) { - $exec = $command->payload->exec; - - if (! isset($exec->data, $exec->code)) { - throw new InvalidArgumentException('Invalid execute command object given'); - } - - $payload = new Payload( - payloadType: $payloadType, - executePayload: new ExecutePayload( - code: $exec->code, - data: (array) ($exec->data ?? []), - ) - ); - } elseif ($payloadType === PayloadType::CONTINUE) { - $cont = $command->payload->cont; - - if (! isset($cont->data, $cont->code, $cont->pactId, $cont->rollback, $cont->step)) { - throw new InvalidArgumentException('Invalid continue command object given'); - } - - $payload = new Payload( - payloadType: $payloadType, - continuePayload: new ContinuePayload( - pactId: $cont->pactId, - rollback: $cont->rollback, - step: $cont->step, - proof: $cont->proof ?? null, - data: (array) ($cont->data ?? []), - ) - ); - } else { - throw new InvalidArgumentException('Unknown payload type'); - } - - $commandObject = new self( - meta: new Meta( - creationTime: Carbon::createFromTimestamp($meta->creationTime), - ttl: $meta->ttl, - gasLimit: $meta->gasLimit, - chainId: $meta->chainId, - gasPrice: $meta->gasPrice, - sender: $meta->sender - ), - payload: $payload, - networkId: $command->networkId ?? null, - nonce: $command->nonce ?? null - ); - - $commandObject->setSigners(array_map(static function (array|object $signer) { - $signer = (array) $signer; - - return $signer['pubKey']; - }, $command->signers)); - - return $commandObject; - } -} diff --git a/src/Pact/CommandFactory.php b/src/Pact/CommandFactory.php new file mode 100644 index 0000000..8839ec6 --- /dev/null +++ b/src/Pact/CommandFactory.php @@ -0,0 +1,136 @@ +payload->payloadType === PayloadType::EXECUTE) { + $self->withExecutePayload($command->payload->executePayload); + } + + if ($command->payload->payloadType === PayloadType::CONTINUE) { + $self->withContinuePayload($command->payload->continuePayload); + } + + return $self->withSigners($command->signers) + ->withNetworkId($command->networkId) + ->withNonce($command->nonce) + ->withMetadata($command->meta); + } + + public function addSigner(Signer $signer): self + { + if (! isset($this->signers)) { + $this->signers = new SignerCollection(); + } + + $signers = $this->signers->toArray(); + $signers[] = $signer; + + $this->signers = new SignerCollection(...$signers); + } + + public function withMetadata(Metadata $metadata): self + { + $this->metadata = $metadata; + + return $this; + } + + public function withExecutePayload(ExecutePayload $payload): self + { + $this->payload = new Payload( + payloadType: PayloadType::EXECUTE, + executePayload: $payload + ); + + return $this; + } + + public function withContinuePayload(ContinuePayload $payload): self + { + $this->payload = new Payload( + payloadType: PayloadType::CONTINUE, + continuePayload: $payload + ); + + return $this; + } + + public function withNonce(string $nonce): self + { + $this->nonce = $nonce; + + return $this; + } + + public function withNetworkId(string $networkId): self + { + $this->networkId = $networkId; + + return $this; + } + + public function withSigners(SignerCollection $signers): self + { + $this->signers = $signers; + + return $this; + } + + /** + * @throws MissingPayloadException + */ + public function make(): Command + { + if (! isset($this->payload)) { + throw new MissingPayloadException('Payload is required to build command'); + } + + if (! isset($this->metadata)) { + $this->metadata = (new MetadataFactory())->make(); + } + + if (! isset($this->nonce)) { + $this->nonce = Carbon::now()->toISOString(); + } + + if (! isset($this->networkId)) { + $this->networkId = '0'; + } + + if (! isset($this->signers)) { + $this->signers = new SignerCollection(); + } + + return new Command( + meta: $this->metadata, + payload: $this->payload, + networkId: $this->networkId, + nonce: $this->nonce, + signers: $this->signers + ); + } +} diff --git a/src/Pact/Meta.php b/src/Pact/Meta.php deleted file mode 100644 index 1a9eab3..0000000 --- a/src/Pact/Meta.php +++ /dev/null @@ -1,44 +0,0 @@ - $this->creationTime->getTimestamp(), - 'ttl' => $this->ttl, - 'gasLimit' => $this->gasLimit, - 'chainId' => $this->chainId, - 'gasPrice' => $this->gasPrice, - 'sender' => $this->sender, - ]; - } - - public static function create(?array $options = []): self - { - return new self( - creationTime: (isset($options['creationTime'])) ? Carbon::createFromTimestamp((int) $options['creationTime']) : Carbon::now(), - ttl: (int) ($options['ttl'] ?? 7200), - gasLimit: (int) ($options['gasLimit'] ?? 10000), - chainId: (string) ($options['chainId'] ?? '0'), - gasPrice: (float) ($options['gasPrice'] ?? self::MIN_GAS_PRICE), - sender: (string) ($options['sender'] ?? '') - ); - } -} diff --git a/src/Pact/MetadataFactory.php b/src/Pact/MetadataFactory.php new file mode 100644 index 0000000..cb2c582 --- /dev/null +++ b/src/Pact/MetadataFactory.php @@ -0,0 +1,47 @@ + $option) { + if (in_array($key, get_class_vars(self::class), true)) { + if ($key === 'creationTime' && ! is_a($option, Carbon::class)) { + $this->$key = Carbon::createFromTimestamp($option); + } else { + $this->$key = $option; + } + } + } + + return $this; + } + + public function make(): Metadata + { + if (! isset($this->creationTime)) { + $this->creationTime = Carbon::now(); + } + + return new Metadata( + creationTime: $this->creationTime, + ttl: $this->ttl, + gasLimit: $this->gasLimit, + chainId: $this->chainId, + gasPrice: $this->gasPrice, + sender: $this->sender + ); + } +} diff --git a/src/Pact/RequestKeyCollection.php b/src/Pact/RequestKeyCollection.php deleted file mode 100644 index 82bd60b..0000000 --- a/src/Pact/RequestKeyCollection.php +++ /dev/null @@ -1,36 +0,0 @@ -array = $requestKey; - } - - /** - * @return RequestKey[] - */ - public function toArray(): array - { - return $this->array; - } - - public function toPlainArray(): array - { - return array_map(static function (RequestKey $requestKey) { - return $requestKey->key; - }, $this->array); - } - - public function first(): RequestKey - { - return $this->array[0]; - } -} diff --git a/src/Pact/SignedCommand.php b/src/Pact/SignedCommand.php deleted file mode 100644 index a286da0..0000000 --- a/src/Pact/SignedCommand.php +++ /dev/null @@ -1,71 +0,0 @@ -validateSignatures(); - } - - private function validateSignatures(): void - { - foreach ($this->signatures->toArray() as $signature) { - if ($signature->hash !== $this->hash) { - throw new InvalidArgumentException("Signatures for different hashes found: {$signature->hash}, expected: {$this->hash}"); - } - } - } - - /** - * @throws JsonException - */ - public function toArray(): array - { - $signatures = []; - - foreach ($this->signatures->toArray() as $signature) { - $signatures[] = ['sig' => $signature->signature]; - } - - return [ - 'hash' => $this->hash, - 'sigs' => $signatures, - 'cmd' => $this->command->toString(), - ]; - } - - /** - * @throws JsonException - */ - public function toString(): string - { - return json_encode($this->toArray(), JSON_THROW_ON_ERROR); - } - - /** - * @throws JsonException - */ - public static function fromString(string $commandJson): self - { - $command = json_decode($commandJson, false, 512, JSON_THROW_ON_ERROR); - - if (! isset($command->hash, $command->sigs, $command->cmd)) { - throw new InvalidArgumentException('Invalid Signed Command JSON string given'); - } - - return new self( - hash: $command->hash, - signatures: SignatureCollection::fromArray((array) $command->sigs, $command->hash), - command: Command::fromString($command->cmd), - ); - } -} diff --git a/src/Pact/SignedCommandCollection.php b/src/Pact/SignedCommandCollection.php deleted file mode 100644 index ceaf50c..0000000 --- a/src/Pact/SignedCommandCollection.php +++ /dev/null @@ -1,51 +0,0 @@ -array = $signedCommand; - } - - /** - * @return SignedCommand[] - */ - public function toArray(): array - { - return $this->array; - } - - public function toPayload(): array - { - $commands = array_map([$this, 'mapToArray'], $this->array); - - return [ - 'cmds' => $commands - ]; - } - - /** - * @throws JsonException - */ - public function toPayloadString(): string - { - return json_encode($this->toPayload(), JSON_THROW_ON_ERROR); - } - - /** - * @throws JsonException - */ - private function mapToArray(SignedCommand $signedCommand): array - { - return $signedCommand->toArray(); - } -} diff --git a/src/Traits/HasCollectionMethods.php b/src/Traits/HasCollectionMethods.php new file mode 100644 index 0000000..5171f32 --- /dev/null +++ b/src/Traits/HasCollectionMethods.php @@ -0,0 +1,28 @@ +array; + } + + public function first(): mixed + { + return $this->array[0]; + } + + public function get(int $offset): mixed + { + return $this->array[$offset]; + } + + public function count(): int + { + return sizeof($this->array); + } +} diff --git a/src/ValueObjects/Command/Command.php b/src/ValueObjects/Command/Command.php new file mode 100644 index 0000000..8adb3c3 --- /dev/null +++ b/src/ValueObjects/Command/Command.php @@ -0,0 +1,18 @@ + $this->proof, - 'pactId' => $this->pactId, - 'rollback' => $this->rollback, - 'step' => $this->step, - 'data' => $this->data, - ]; - } } diff --git a/src/Pact/ExecutePayload.php b/src/ValueObjects/Command/Payload/ExecutePayload.php similarity index 52% rename from src/Pact/ExecutePayload.php rename to src/ValueObjects/Command/Payload/ExecutePayload.php index 2ea1e8d..04cacaa 100644 --- a/src/Pact/ExecutePayload.php +++ b/src/ValueObjects/Command/Payload/ExecutePayload.php @@ -1,6 +1,6 @@ $this->data, - 'code' => $this->code, - ]; - } } diff --git a/src/Pact/Payload.php b/src/ValueObjects/Command/Payload/Payload.php similarity index 79% rename from src/Pact/Payload.php rename to src/ValueObjects/Command/Payload/Payload.php index 5f94b58..4116295 100644 --- a/src/Pact/Payload.php +++ b/src/ValueObjects/Command/Payload/Payload.php @@ -1,6 +1,6 @@ payloadType->value => - $this->executePayload?->toArray() - ?? $this->continuePayload->toArray(), - ]; - } } diff --git a/src/Pact/PayloadType.php b/src/ValueObjects/Command/Payload/PayloadType.php similarity index 71% rename from src/Pact/PayloadType.php rename to src/ValueObjects/Command/Payload/PayloadType.php index 398bfd2..5181286 100644 --- a/src/Pact/PayloadType.php +++ b/src/ValueObjects/Command/Payload/PayloadType.php @@ -1,6 +1,6 @@ validateSignatures(); + } + + private function validateSignatures(): void + { + foreach ($this->signatures->toArray() as $signature) { + if ($signature->hash !== $this->hash) { + throw new InvalidArgumentException("Signatures for different hashes found: {$signature->hash}, expected: {$this->hash}"); + } + } + } +} diff --git a/src/ValueObjects/Command/SignedCommandCollection.php b/src/ValueObjects/Command/SignedCommandCollection.php new file mode 100644 index 0000000..ff79ab2 --- /dev/null +++ b/src/ValueObjects/Command/SignedCommandCollection.php @@ -0,0 +1,16 @@ +array = $signedCommand; + } +} diff --git a/src/Pact/RequestKey.php b/src/ValueObjects/RequestKey/RequestKey.php similarity index 77% rename from src/Pact/RequestKey.php rename to src/ValueObjects/RequestKey/RequestKey.php index 057d231..fd9c79c 100644 --- a/src/Pact/RequestKey.php +++ b/src/ValueObjects/RequestKey/RequestKey.php @@ -1,6 +1,6 @@ array = $requestKey; + } + + public function toPlainArray(): array + { + return array_map(static function (RequestKey $requestKey) { + return $requestKey->key; + }, $this->toArray()); + } +} diff --git a/src/ValueObjects/Signer/Capability.php b/src/ValueObjects/Signer/Capability.php new file mode 100644 index 0000000..cb0e045 --- /dev/null +++ b/src/ValueObjects/Signer/Capability.php @@ -0,0 +1,12 @@ +array = $capabilityDescription; + } +} diff --git a/src/ValueObjects/Signer/KeyPair.php b/src/ValueObjects/Signer/KeyPair.php new file mode 100644 index 0000000..fced80f --- /dev/null +++ b/src/ValueObjects/Signer/KeyPair.php @@ -0,0 +1,12 @@ +array = $keyPair; + } +} diff --git a/src/ValueObjects/Signer/PublicKey.php b/src/ValueObjects/Signer/PublicKey.php new file mode 100644 index 0000000..0948a4a --- /dev/null +++ b/src/ValueObjects/Signer/PublicKey.php @@ -0,0 +1,24 @@ +key->getRawKeyMaterial()); + } + + public function __toString(): string + { + return $this->toString(); + } +} diff --git a/src/ValueObjects/Signer/SecretKey.php b/src/ValueObjects/Signer/SecretKey.php new file mode 100644 index 0000000..b94b10d --- /dev/null +++ b/src/ValueObjects/Signer/SecretKey.php @@ -0,0 +1,13 @@ +array = $signature; + } +} diff --git a/src/ValueObjects/Signer/Signer.php b/src/ValueObjects/Signer/Signer.php new file mode 100644 index 0000000..bf009bb --- /dev/null +++ b/src/ValueObjects/Signer/Signer.php @@ -0,0 +1,12 @@ +array = $signer; + } +} diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php index 63f1cdd..f9f3ae3 100644 --- a/tests/Unit/ClientTest.php +++ b/tests/Unit/ClientTest.php @@ -4,17 +4,17 @@ use Carbon\Carbon; use Kadena\Client; -use Kadena\Crypto\Signature; -use Kadena\Crypto\SignatureCollection; -use Kadena\Pact\Command; -use Kadena\Pact\ExecutePayload; -use Kadena\Pact\Meta; -use Kadena\Pact\Payload; -use Kadena\Pact\PayloadType; -use Kadena\Pact\RequestKey; -use Kadena\Pact\RequestKeyCollection; -use Kadena\Pact\SignedCommand; -use Kadena\Pact\SignedCommandCollection; +use Kadena\ValueObjects\Command\Command; +use Kadena\ValueObjects\Command\Metadata; +use Kadena\ValueObjects\Command\Payload\ExecutePayload; +use Kadena\ValueObjects\Command\Payload\Payload; +use Kadena\ValueObjects\Command\Payload\PayloadType; +use Kadena\ValueObjects\Command\SignedCommand; +use Kadena\ValueObjects\Command\SignedCommandCollection; +use Kadena\ValueObjects\RequestKey\RequestKey; +use Kadena\ValueObjects\RequestKey\RequestKeyCollection; +use Kadena\ValueObjects\Signer\Signature; +use Kadena\ValueObjects\Signer\SignatureCollection; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; @@ -28,7 +28,7 @@ public function setUp(): void parent::setUp(); $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, diff --git a/tests/Unit/Crypto/KeyPairCollectionTest.php b/tests/Unit/Crypto/KeyPairCollectionTest.php index f58567e..7cfe547 100644 --- a/tests/Unit/Crypto/KeyPairCollectionTest.php +++ b/tests/Unit/Crypto/KeyPairCollectionTest.php @@ -2,8 +2,8 @@ namespace Kadena\Tests\Unit\Crypto; -use Kadena\Crypto\KeyPair; -use Kadena\Crypto\KeyPairCollection; +use Kadena\ValueObjects\Signer\KeyPair; +use Kadena\ValueObjects\Signer\KeyPairCollection; use PHPUnit\Framework\TestCase; final class KeyPairCollectionTest extends TestCase diff --git a/tests/Unit/Crypto/KeyPairTest.php b/tests/Unit/Crypto/KeyPairTest.php index 3460965..dbd8ed6 100644 --- a/tests/Unit/Crypto/KeyPairTest.php +++ b/tests/Unit/Crypto/KeyPairTest.php @@ -2,7 +2,7 @@ namespace Kadena\Tests\Unit\Crypto; -use Kadena\Crypto\KeyPair; +use Kadena\ValueObjects\Signer\KeyPair; use ParagonIE\Halite\Asymmetric\SignaturePublicKey; use ParagonIE\Halite\Asymmetric\SignatureSecretKey; use PHPUnit\Framework\TestCase; diff --git a/tests/Unit/Crypto/SignatureCollectionTest.php b/tests/Unit/Crypto/SignatureCollectionTest.php index 49a937d..18343bd 100644 --- a/tests/Unit/Crypto/SignatureCollectionTest.php +++ b/tests/Unit/Crypto/SignatureCollectionTest.php @@ -2,8 +2,8 @@ namespace Kadena\Tests\Unit\Crypto; -use Kadena\Crypto\Signature; -use Kadena\Crypto\SignatureCollection; +use Kadena\ValueObjects\Signer\Signature; +use Kadena\ValueObjects\Signer\SignatureCollection; use PHPUnit\Framework\TestCase; final class SignatureCollectionTest extends TestCase diff --git a/tests/Unit/Crypto/SignerTest.php b/tests/Unit/Crypto/SignerTest.php index 909f694..ed8d895 100644 --- a/tests/Unit/Crypto/SignerTest.php +++ b/tests/Unit/Crypto/SignerTest.php @@ -4,14 +4,14 @@ use Carbon\Carbon; use Kadena\Crypto\Hash; -use Kadena\Crypto\KeyPair; -use Kadena\Crypto\Signature; -use Kadena\Crypto\Signer; -use Kadena\Pact\Command; -use Kadena\Pact\ExecutePayload; -use Kadena\Pact\Meta; -use Kadena\Pact\Payload; -use Kadena\Pact\PayloadType; +use Kadena\Crypto\MessageSigner; +use Kadena\ValueObjects\Command\Command; +use Kadena\ValueObjects\Command\Metadata; +use Kadena\ValueObjects\Command\Payload\ExecutePayload; +use Kadena\ValueObjects\Command\Payload\Payload; +use Kadena\ValueObjects\Command\Payload\PayloadType; +use Kadena\ValueObjects\Signer\KeyPair; +use Kadena\ValueObjects\Signer\Signature; use ParagonIE\ConstantTime\Base64UrlSafe; use PHPUnit\Framework\TestCase; @@ -22,13 +22,13 @@ public function it_should_sign_a_message_with_a_key_pair_and_return_a_signed_mes { $message = 'test message'; $keyPair = KeyPair::generate(); - $signature = Signer::sign($message, $keyPair); + $signature = MessageSigner::sign($message, $keyPair); $expectedHash = Base64UrlSafe::encodeUnpadded(Hash::generic($message)); $this->assertInstanceOf(Signature::class, $signature); $this->assertSame($expectedHash, $signature->hash); - $this->assertTrue(Signer::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); + $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); } /** @test */ @@ -37,11 +37,11 @@ public function it_should_sign_a_hash_with_a_key_pair_and_return_a_signed_messag $message = 'test message'; $keyPair = KeyPair::generate(); $hash = Hash::generic($message); - $signature = Signer::signHash($hash, $keyPair); + $signature = MessageSigner::signHash($hash, $keyPair); $this->assertInstanceOf(Signature::class, $signature); $this->assertSame(Base64UrlSafe::encodeUnpadded($hash), $signature->hash); - $this->assertTrue(Signer::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); + $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); } /** @test */ @@ -49,9 +49,9 @@ public function it_should_verify_a_signature_with_a_public_key_and_message(): vo { $keyPair = KeyPair::generate(); $message = 'test message'; - $signature = Signer::sign($message, $keyPair); + $signature = MessageSigner::sign($message, $keyPair); - $this->assertTrue(Signer::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); + $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); } /** @test */ @@ -60,7 +60,7 @@ public function it_should_sign_a_command_with_a_key_pair_and_return_a_signature_ $keyPair = KeyPair::generate(); $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -76,12 +76,12 @@ public function it_should_sign_a_command_with_a_key_pair_and_return_a_signature_ ) ); - $signature = Signer::signCommand($command, $keyPair); + $signature = MessageSigner::signCommand($command, $keyPair); $expectedHash = Base64UrlSafe::encodeUnpadded(Hash::generic($command->toString())); $this->assertInstanceOf(Signature::class, $signature); $this->assertSame($expectedHash, $signature->hash); - $this->assertTrue(Signer::verifySignature(Hash::generic($command->toString()), $signature->signature, $keyPair->publicKey)); + $this->assertTrue(MessageSigner::verifySignature(Hash::generic($command->toString()), $signature->signature, $keyPair->publicKey)); } } diff --git a/tests/Unit/Pact/CommandTest.php b/tests/Unit/Pact/CommandTest.php index 02ceaa0..a85381b 100644 --- a/tests/Unit/Pact/CommandTest.php +++ b/tests/Unit/Pact/CommandTest.php @@ -4,15 +4,15 @@ use Carbon\Carbon; use Kadena\Crypto\Hash; -use Kadena\Crypto\KeyPair; -use Kadena\Crypto\KeyPairCollection; -use Kadena\Crypto\Signer; -use Kadena\Pact\Command; -use Kadena\Pact\ExecutePayload; -use Kadena\Pact\Meta; -use Kadena\Pact\Payload; -use Kadena\Pact\PayloadType; -use Kadena\Pact\SignedCommand; +use Kadena\Crypto\MessageSigner; +use Kadena\ValueObjects\Command\Command; +use Kadena\ValueObjects\Command\Metadata; +use Kadena\ValueObjects\Command\Payload\ExecutePayload; +use Kadena\ValueObjects\Command\Payload\Payload; +use Kadena\ValueObjects\Command\Payload\PayloadType; +use Kadena\ValueObjects\Command\SignedCommand; +use Kadena\ValueObjects\Signer\KeyPair; +use Kadena\ValueObjects\Signer\KeyPairCollection; use ParagonIE\ConstantTime\Base64UrlSafe; use ParagonIE\ConstantTime\Hex; use PHPUnit\Framework\TestCase; @@ -30,7 +30,7 @@ protected function setUp(): void public function it_should_set_nonce_to_current_time_if_not_defined(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -53,7 +53,7 @@ public function it_should_set_nonce_to_current_time_if_not_defined(): void public function it_should_cast_to_array(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -96,7 +96,7 @@ public function it_should_cast_to_array(): void public function it_should_cast_to_string(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -139,7 +139,7 @@ public function it_should_cast_to_string(): void public function it_should_construct_from_string(): void { $expected = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -182,7 +182,7 @@ public function it_should_construct_from_string(): void public function it_should_be_able_to_set_signers(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -252,7 +252,7 @@ public function it_should_be_able_to_set_signers(): void public function it_should_return_a_signed_command_object_when_using_get_signed_command(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -279,6 +279,6 @@ public function it_should_return_a_signed_command_object_when_using_get_signed_c $this->assertInstanceOf(SignedCommand::class, $signedCommand); $this->assertSame($expectedHash, $signedCommand->hash); - $this->assertTrue(Signer::verifySignature(Hash::generic($message), $signedCommand->signatures->first()->signature, $keyPair->publicKey)); + $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signedCommand->signatures->first()->signature, $keyPair->publicKey)); } } diff --git a/tests/Unit/Pact/ContinuePayloadTest.php b/tests/Unit/Pact/ContinuePayloadTest.php index e4d4405..8468308 100644 --- a/tests/Unit/Pact/ContinuePayloadTest.php +++ b/tests/Unit/Pact/ContinuePayloadTest.php @@ -2,7 +2,7 @@ namespace Kadena\Tests\Unit\Pact; -use Kadena\Pact\ContinuePayload; +use Kadena\ValueObjects\Command\Payload\ContinuePayload; use PHPUnit\Framework\TestCase; final class ContinuePayloadTest extends TestCase diff --git a/tests/Unit/Pact/ExecutePayloadTest.php b/tests/Unit/Pact/ExecutePayloadTest.php index cf881ba..5cd7aff 100644 --- a/tests/Unit/Pact/ExecutePayloadTest.php +++ b/tests/Unit/Pact/ExecutePayloadTest.php @@ -2,7 +2,7 @@ namespace Kadena\Tests\Unit\Pact; -use Kadena\Pact\ExecutePayload; +use Kadena\ValueObjects\Command\Payload\ExecutePayload; use PHPUnit\Framework\TestCase; final class ExecutePayloadTest extends TestCase diff --git a/tests/Unit/Pact/MetaTest.php b/tests/Unit/Pact/MetaTest.php index 7fd5b61..cc2d640 100644 --- a/tests/Unit/Pact/MetaTest.php +++ b/tests/Unit/Pact/MetaTest.php @@ -3,7 +3,7 @@ namespace Kadena\Tests\Unit\Pact; use Carbon\Carbon; -use Kadena\Pact\Meta; +use Kadena\ValueObjects\Command\Metadata; use PHPUnit\Framework\TestCase; final class MetaTest extends TestCase @@ -25,7 +25,7 @@ public function it_should_return_the_correct_array_representation_of_the_meta_ob $gasPrice = 1.25; $sender = 'test-sender'; - $meta = new Meta($creationTime, $ttl, $gasLimit, $chainId, $gasPrice, $sender); + $meta = new Metadata($creationTime, $ttl, $gasLimit, $chainId, $gasPrice, $sender); $expectedArray = [ 'creationTime' => $creationTime->getTimestamp(), 'ttl' => $ttl, @@ -48,7 +48,7 @@ public function it_should_be_able_to_be_constructed_from_an_options_array(): voi $gasPrice = 1.25; $sender = 'test-sender'; - $expected = new Meta($creationTime, $ttl, $gasLimit, $chainId, $gasPrice, $sender); + $expected = new Metadata($creationTime, $ttl, $gasLimit, $chainId, $gasPrice, $sender); $options = [ 'creationTime' => $creationTime->getTimestamp(), @@ -59,7 +59,7 @@ public function it_should_be_able_to_be_constructed_from_an_options_array(): voi 'sender' => $sender ]; - $actual = Meta::create($options); + $actual = Metadata::create($options); $this->assertEquals($expected, $actual); } @@ -74,9 +74,9 @@ public function it_should_be_able_to_be_constructed_using_the_create_method_with $gasPrice = 1e-8; $sender = ''; - $expected = new Meta($creationTime, $ttl, $gasLimit, $chainId, $gasPrice, $sender); + $expected = new Metadata($creationTime, $ttl, $gasLimit, $chainId, $gasPrice, $sender); - $actual = Meta::create(); + $actual = Metadata::create(); $this->assertEquals($expected, $actual); } diff --git a/tests/Unit/Pact/PayloadTest.php b/tests/Unit/Pact/PayloadTest.php index 75e39d3..213a799 100644 --- a/tests/Unit/Pact/PayloadTest.php +++ b/tests/Unit/Pact/PayloadTest.php @@ -3,10 +3,10 @@ namespace Kadena\Tests\Unit\Pact; use InvalidArgumentException; -use Kadena\Pact\ContinuePayload; -use Kadena\Pact\ExecutePayload; -use Kadena\Pact\Payload; -use Kadena\Pact\PayloadType; +use Kadena\ValueObjects\Command\Payload\ContinuePayload; +use Kadena\ValueObjects\Command\Payload\ExecutePayload; +use Kadena\ValueObjects\Command\Payload\Payload; +use Kadena\ValueObjects\Command\Payload\PayloadType; use PHPUnit\Framework\TestCase; final class PayloadTest extends TestCase diff --git a/tests/Unit/Pact/RequestKeyCollectionTest.php b/tests/Unit/Pact/RequestKeyCollectionTest.php index a660754..d32cbb5 100644 --- a/tests/Unit/Pact/RequestKeyCollectionTest.php +++ b/tests/Unit/Pact/RequestKeyCollectionTest.php @@ -2,8 +2,8 @@ namespace Kadena\Tests\Unit\Pact; -use Kadena\Pact\RequestKey; -use Kadena\Pact\RequestKeyCollection; +use Kadena\ValueObjects\RequestKey\RequestKey; +use Kadena\ValueObjects\RequestKey\RequestKeyCollection; use PHPUnit\Framework\TestCase; final class RequestKeyCollectionTest extends TestCase diff --git a/tests/Unit/Pact/SignedCommandCollectionTest.php b/tests/Unit/Pact/SignedCommandCollectionTest.php index 194b7c3..8207ab3 100644 --- a/tests/Unit/Pact/SignedCommandCollectionTest.php +++ b/tests/Unit/Pact/SignedCommandCollectionTest.php @@ -3,15 +3,15 @@ namespace Kadena\Tests\Unit\Pact; use Carbon\Carbon; -use Kadena\Crypto\Signature; -use Kadena\Crypto\SignatureCollection; -use Kadena\Pact\Command; -use Kadena\Pact\ExecutePayload; -use Kadena\Pact\Meta; -use Kadena\Pact\Payload; -use Kadena\Pact\PayloadType; -use Kadena\Pact\SignedCommand; -use Kadena\Pact\SignedCommandCollection; +use Kadena\ValueObjects\Command\Command; +use Kadena\ValueObjects\Command\Metadata; +use Kadena\ValueObjects\Command\Payload\ExecutePayload; +use Kadena\ValueObjects\Command\Payload\Payload; +use Kadena\ValueObjects\Command\Payload\PayloadType; +use Kadena\ValueObjects\Command\SignedCommand; +use Kadena\ValueObjects\Command\SignedCommandCollection; +use Kadena\ValueObjects\Signer\Signature; +use Kadena\ValueObjects\Signer\SignatureCollection; use PHPUnit\Framework\TestCase; final class SignedCommandCollectionTest extends TestCase @@ -27,7 +27,7 @@ protected function setUp(): void public function it_should_convert_to_an_array(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -64,7 +64,7 @@ public function it_should_convert_to_an_array(): void public function it_should_convert_in_to_a_payload_array(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -115,7 +115,7 @@ public function it_should_convert_in_to_a_payload_array(): void public function it_should_convert_in_to_a_json_payload_string(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, diff --git a/tests/Unit/Pact/SignedCommandTest.php b/tests/Unit/Pact/SignedCommandTest.php index b0733d7..ae51a8f 100644 --- a/tests/Unit/Pact/SignedCommandTest.php +++ b/tests/Unit/Pact/SignedCommandTest.php @@ -4,14 +4,14 @@ use Carbon\Carbon; use InvalidArgumentException; -use Kadena\Crypto\Signature; -use Kadena\Crypto\SignatureCollection; -use Kadena\Pact\Command; -use Kadena\Pact\ExecutePayload; -use Kadena\Pact\Meta; -use Kadena\Pact\Payload; -use Kadena\Pact\PayloadType; -use Kadena\Pact\SignedCommand; +use Kadena\ValueObjects\Command\Command; +use Kadena\ValueObjects\Command\Metadata; +use Kadena\ValueObjects\Command\Payload\ExecutePayload; +use Kadena\ValueObjects\Command\Payload\Payload; +use Kadena\ValueObjects\Command\Payload\PayloadType; +use Kadena\ValueObjects\Command\SignedCommand; +use Kadena\ValueObjects\Signer\Signature; +use Kadena\ValueObjects\Signer\SignatureCollection; use PHPUnit\Framework\TestCase; final class SignedCommandTest extends TestCase @@ -27,7 +27,7 @@ protected function setUp(): void public function it_should_create_signed_command_from_string(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -67,7 +67,7 @@ public function it_should_create_signed_command_from_string(): void public function it_should_validate_signatures_when_creating(): void { $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -113,7 +113,7 @@ public function it_should_return_the_expected_array(): void $signatureCollection = new SignatureCollection($signature1, $signature2); $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, @@ -151,7 +151,7 @@ public function it_should_return_the_expected_string(): void $signatureCollection = new SignatureCollection($signature1, $signature2); $command = new Command( - meta: new Meta( + meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), ttl: 0, gasLimit: 0, From 1abd384729af2e90aa0301ea043ad349e5cd52bf Mon Sep 17 00:00:00 2001 From: Hergen Dillema Date: Sun, 8 Jan 2023 20:40:45 +0100 Subject: [PATCH 2/7] codestyle fix --- src/DataMappers/SignedCommandCollectionMapper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataMappers/SignedCommandCollectionMapper.php b/src/DataMappers/SignedCommandCollectionMapper.php index 9d3e3a6..d93509f 100644 --- a/src/DataMappers/SignedCommandCollectionMapper.php +++ b/src/DataMappers/SignedCommandCollectionMapper.php @@ -14,7 +14,7 @@ final class SignedCommandCollectionMapper public static function toArray(SignedCommandCollection $commands): array { return [ - 'cmds' => array_map(static function (SignedCommand $command){ + 'cmds' => array_map(static function (SignedCommand $command) { return SignedCommandMapper::toArray($command); }, $commands->toArray()) ]; From 31efc0c269d5238537d2384ec16863b819d0a96f Mon Sep 17 00:00:00 2001 From: Hergen Dillema Date: Sun, 8 Jan 2023 21:15:45 +0100 Subject: [PATCH 3/7] Moved trait, added some tests --- .../Command/SignedCommandCollection.php | 2 +- .../HasCollectionMethods.php | 2 +- .../RequestKey/RequestKeyCollection.php | 2 +- .../Signer/CapabilityCollection.php | 2 +- src/ValueObjects/Signer/KeyPairCollection.php | 2 +- .../Signer/SignatureCollection.php | 2 +- src/ValueObjects/Signer/SignerCollection.php | 2 +- tests/Unit/Crypto/CommandSignerTest.php | 36 ++++++++ tests/Unit/Crypto/KeyFactoryTest.php | 22 +++++ tests/Unit/Crypto/KeyPairCollectionTest.php | 20 ----- tests/Unit/Crypto/KeyPairTest.php | 21 ----- tests/Unit/Crypto/MessageSignerTest.php | 50 +++++++++++ tests/Unit/Crypto/SignatureCollectionTest.php | 45 ---------- tests/Unit/Crypto/SignerTest.php | 87 ------------------- 14 files changed, 115 insertions(+), 180 deletions(-) rename src/{Traits => ValueObjects}/HasCollectionMethods.php (93%) create mode 100644 tests/Unit/Crypto/CommandSignerTest.php create mode 100644 tests/Unit/Crypto/KeyFactoryTest.php delete mode 100644 tests/Unit/Crypto/KeyPairCollectionTest.php delete mode 100644 tests/Unit/Crypto/KeyPairTest.php create mode 100644 tests/Unit/Crypto/MessageSignerTest.php delete mode 100644 tests/Unit/Crypto/SignatureCollectionTest.php delete mode 100644 tests/Unit/Crypto/SignerTest.php diff --git a/src/ValueObjects/Command/SignedCommandCollection.php b/src/ValueObjects/Command/SignedCommandCollection.php index ff79ab2..72535de 100644 --- a/src/ValueObjects/Command/SignedCommandCollection.php +++ b/src/ValueObjects/Command/SignedCommandCollection.php @@ -3,7 +3,7 @@ namespace Kadena\ValueObjects\Command; use Kadena\Contracts\Collection; -use Kadena\Traits\HasCollectionMethods; +use Kadena\ValueObjects\HasCollectionMethods; final class SignedCommandCollection implements Collection { diff --git a/src/Traits/HasCollectionMethods.php b/src/ValueObjects/HasCollectionMethods.php similarity index 93% rename from src/Traits/HasCollectionMethods.php rename to src/ValueObjects/HasCollectionMethods.php index 5171f32..a5b351e 100644 --- a/src/Traits/HasCollectionMethods.php +++ b/src/ValueObjects/HasCollectionMethods.php @@ -1,6 +1,6 @@ withExecutePayload(new ExecutePayload( + code: '(+ 2 2)' + ))->make(); + + $signed = CommandSigner::sign($command, new KeyPairCollection($keyPair)); + + $expectedHash = Base64UrlSafe::encodeUnpadded(Hash::generic(CommandMapper::toString($command))); + + $this->assertInstanceOf(SignedCommand::class, $signed); + $this->assertSame($expectedHash, $signed->hash); + $this->assertTrue(MessageSigner::verifySignature(Hash::generic(CommandMapper::toString($command)), $signed->signatures->first()->signature, $keyPair->publicKey->key)); + } +} diff --git a/tests/Unit/Crypto/KeyFactoryTest.php b/tests/Unit/Crypto/KeyFactoryTest.php new file mode 100644 index 0000000..a7af8fd --- /dev/null +++ b/tests/Unit/Crypto/KeyFactoryTest.php @@ -0,0 +1,22 @@ +assertInstanceOf(KeyPair::class, $keyPair); + $this->assertInstanceOf(PublicKey::class, $keyPair->publicKey); + $this->assertInstanceOf(SecretKey::class, $keyPair->secretKey); + } +} diff --git a/tests/Unit/Crypto/KeyPairCollectionTest.php b/tests/Unit/Crypto/KeyPairCollectionTest.php deleted file mode 100644 index 7cfe547..0000000 --- a/tests/Unit/Crypto/KeyPairCollectionTest.php +++ /dev/null @@ -1,20 +0,0 @@ -assertEquals([$keyPair1, $keyPair2], $keyPairCollection->toArray()); - } -} diff --git a/tests/Unit/Crypto/KeyPairTest.php b/tests/Unit/Crypto/KeyPairTest.php deleted file mode 100644 index dbd8ed6..0000000 --- a/tests/Unit/Crypto/KeyPairTest.php +++ /dev/null @@ -1,21 +0,0 @@ -assertInstanceOf(KeyPair::class, $keyPair); - $this->assertInstanceOf(SignaturePublicKey::class, $keyPair->publicKey); - $this->assertInstanceOf(SignatureSecretKey::class, $keyPair->secretKey); - } -} diff --git a/tests/Unit/Crypto/MessageSignerTest.php b/tests/Unit/Crypto/MessageSignerTest.php new file mode 100644 index 0000000..61e85ac --- /dev/null +++ b/tests/Unit/Crypto/MessageSignerTest.php @@ -0,0 +1,50 @@ +assertInstanceOf(Signature::class, $signature); + $this->assertSame($expectedHash, $signature->hash); + $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey->key)); + } + + /** @test */ + public function it_should_sign_a_hash_with_a_key_pair_and_return_a_signed_message_object(): void + { + $message = 'test message'; + $keyPair = KeyFactory::generate(); + $hash = Hash::generic($message); + $signature = MessageSigner::signHash($hash, $keyPair); + + $this->assertInstanceOf(Signature::class, $signature); + $this->assertSame(Base64UrlSafe::encodeUnpadded($hash), $signature->hash); + $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey->key)); + } + + /** @test */ + public function it_should_verify_a_signature_with_a_public_key_and_message(): void + { + $keyPair = KeyFactory::generate(); + $message = 'test message'; + $signature = MessageSigner::sign($message, $keyPair); + + $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey->key)); + } +} diff --git a/tests/Unit/Crypto/SignatureCollectionTest.php b/tests/Unit/Crypto/SignatureCollectionTest.php deleted file mode 100644 index 18343bd..0000000 --- a/tests/Unit/Crypto/SignatureCollectionTest.php +++ /dev/null @@ -1,45 +0,0 @@ -assertSame($signature1, $signatureCollection->first()); - } - - /** @test */ - public function it_should_return_all_signatures(): void - { - $signature1 = new Signature('hash', 'sig1'); - $signature2 = new Signature('hash', 'sig2'); - $signatureCollection = new SignatureCollection($signature1, $signature2); - - $this->assertEquals([$signature1, $signature2], $signatureCollection->toArray()); - } - - /** @test */ - public function it_should_create_a_new_signature_collection_from_an_array(): void - { - $signatures = [ - ['sig' => 'sig1'], - ['sig' => 'sig2'] - ]; - - $signatureCollection = SignatureCollection::fromArray($signatures, 'hash'); - - $this->assertInstanceOf(SignatureCollection::class, $signatureCollection); - $this->assertSame('hash', $signatureCollection->first()->hash); - $this->assertSame('sig1', $signatureCollection->first()->signature); - } -} diff --git a/tests/Unit/Crypto/SignerTest.php b/tests/Unit/Crypto/SignerTest.php deleted file mode 100644 index ed8d895..0000000 --- a/tests/Unit/Crypto/SignerTest.php +++ /dev/null @@ -1,87 +0,0 @@ -assertInstanceOf(Signature::class, $signature); - $this->assertSame($expectedHash, $signature->hash); - $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); - } - - /** @test */ - public function it_should_sign_a_hash_with_a_key_pair_and_return_a_signed_message_object(): void - { - $message = 'test message'; - $keyPair = KeyPair::generate(); - $hash = Hash::generic($message); - $signature = MessageSigner::signHash($hash, $keyPair); - - $this->assertInstanceOf(Signature::class, $signature); - $this->assertSame(Base64UrlSafe::encodeUnpadded($hash), $signature->hash); - $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); - } - - /** @test */ - public function it_should_verify_a_signature_with_a_public_key_and_message(): void - { - $keyPair = KeyPair::generate(); - $message = 'test message'; - $signature = MessageSigner::sign($message, $keyPair); - - $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signature->signature, $keyPair->publicKey)); - } - - /** @test */ - public function it_should_sign_a_command_with_a_key_pair_and_return_a_signature_object(): void - { - $keyPair = KeyPair::generate(); - - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $signature = MessageSigner::signCommand($command, $keyPair); - - $expectedHash = Base64UrlSafe::encodeUnpadded(Hash::generic($command->toString())); - - $this->assertInstanceOf(Signature::class, $signature); - $this->assertSame($expectedHash, $signature->hash); - $this->assertTrue(MessageSigner::verifySignature(Hash::generic($command->toString()), $signature->signature, $keyPair->publicKey)); - } -} From d2e9732eb947d66b14c5a73e6f543cafcb3e3979 Mon Sep 17 00:00:00 2001 From: Hergen Dillema Date: Mon, 9 Jan 2023 10:08:18 +0100 Subject: [PATCH 4/7] Added new tests for refactor --- src/DataMappers/CommandMapper.php | 8 +- src/Pact/MetadataFactory.php | 4 +- tests/Unit/ClientTest.php | 43 ++- tests/Unit/DataMappers/CommandMapperTest.php | 111 +++++++ .../SignedCommandCollectionMapperTest.php | 125 ++++++++ .../DataMappers/SignedCommandMapperTest.php | 132 ++++++++ tests/Unit/Pact/CommandFactoryTest.php | 126 ++++++++ tests/Unit/Pact/CommandTest.php | 284 ------------------ tests/Unit/Pact/ContinuePayloadTest.php | 31 -- tests/Unit/Pact/ExecutePayloadTest.php | 24 -- tests/Unit/Pact/MetaTest.php | 83 ----- tests/Unit/Pact/MetadataFactoryTest.php | 61 ++++ tests/Unit/Pact/PayloadTest.php | 60 ---- tests/Unit/Pact/RequestKeyCollectionTest.php | 43 --- .../Unit/Pact/SignedCommandCollectionTest.php | 151 ---------- tests/Unit/Pact/SignedCommandTest.php | 175 ----------- .../ValueObjects/HasCollectionMethodsTest.php | 88 ++++++ 17 files changed, 682 insertions(+), 867 deletions(-) create mode 100644 tests/Unit/DataMappers/CommandMapperTest.php create mode 100644 tests/Unit/DataMappers/SignedCommandCollectionMapperTest.php create mode 100644 tests/Unit/DataMappers/SignedCommandMapperTest.php create mode 100644 tests/Unit/Pact/CommandFactoryTest.php delete mode 100644 tests/Unit/Pact/CommandTest.php delete mode 100644 tests/Unit/Pact/ContinuePayloadTest.php delete mode 100644 tests/Unit/Pact/ExecutePayloadTest.php delete mode 100644 tests/Unit/Pact/MetaTest.php create mode 100644 tests/Unit/Pact/MetadataFactoryTest.php delete mode 100644 tests/Unit/Pact/PayloadTest.php delete mode 100644 tests/Unit/Pact/RequestKeyCollectionTest.php delete mode 100644 tests/Unit/Pact/SignedCommandCollectionTest.php delete mode 100644 tests/Unit/Pact/SignedCommandTest.php create mode 100644 tests/Unit/ValueObjects/HasCollectionMethodsTest.php diff --git a/src/DataMappers/CommandMapper.php b/src/DataMappers/CommandMapper.php index 09472c6..4c8e2ee 100644 --- a/src/DataMappers/CommandMapper.php +++ b/src/DataMappers/CommandMapper.php @@ -13,8 +13,13 @@ use Kadena\ValueObjects\Command\Payload\PayloadType; use Kadena\ValueObjects\Signer\Capability; use Kadena\ValueObjects\Signer\CapabilityCollection; +use Kadena\ValueObjects\Signer\PublicKey; use Kadena\ValueObjects\Signer\Signer; use Kadena\ValueObjects\Signer\SignerCollection; +use ParagonIE\ConstantTime\Hex; +use ParagonIE\Halite\Alerts\InvalidKey; +use ParagonIE\Halite\Asymmetric\SignaturePublicKey; +use ParagonIE\HiddenString\HiddenString; final class CommandMapper { @@ -95,6 +100,7 @@ public static function toString(Command $command): string /** * @throws JsonException + * @throws InvalidKey */ public static function fromString(string $commandJson): Command { @@ -169,7 +175,7 @@ public static function fromString(string $commandJson): Command } $signers[] = new Signer( - publicKey: $signer->pubKey, + publicKey: new PublicKey(new SignaturePublicKey(new HiddenString(Hex::decode($signer->pubKey)))), capabilities: new CapabilityCollection(...$capabilities) ); } diff --git a/src/Pact/MetadataFactory.php b/src/Pact/MetadataFactory.php index cb2c582..c06b9be 100644 --- a/src/Pact/MetadataFactory.php +++ b/src/Pact/MetadataFactory.php @@ -17,9 +17,9 @@ final class MetadataFactory public function withOptions(array $options): self { foreach ($options as $key => $option) { - if (in_array($key, get_class_vars(self::class), true)) { + if (in_array($key, array_keys(get_class_vars(self::class)), true)) { if ($key === 'creationTime' && ! is_a($option, Carbon::class)) { - $this->$key = Carbon::createFromTimestamp($option); + $this->$key = Carbon::createFromTimestamp((int) $option); } else { $this->$key = $option; } diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php index f9f3ae3..5fb4e9a 100644 --- a/tests/Unit/ClientTest.php +++ b/tests/Unit/ClientTest.php @@ -4,6 +4,9 @@ use Carbon\Carbon; use Kadena\Client; +use Kadena\Crypto\KeyFactory; +use Kadena\DataMappers\SignedCommandCollectionMapper; +use Kadena\DataMappers\SignedCommandMapper; use Kadena\ValueObjects\Command\Command; use Kadena\ValueObjects\Command\Metadata; use Kadena\ValueObjects\Command\Payload\ExecutePayload; @@ -13,8 +16,12 @@ use Kadena\ValueObjects\Command\SignedCommandCollection; use Kadena\ValueObjects\RequestKey\RequestKey; use Kadena\ValueObjects\RequestKey\RequestKeyCollection; +use Kadena\ValueObjects\Signer\Capability; +use Kadena\ValueObjects\Signer\CapabilityCollection; use Kadena\ValueObjects\Signer\Signature; use Kadena\ValueObjects\Signer\SignatureCollection; +use Kadena\ValueObjects\Signer\Signer; +use Kadena\ValueObjects\Signer\SignerCollection; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; @@ -27,6 +34,8 @@ public function setUp(): void { parent::setUp(); + $keyPair = KeyFactory::generate(); + $command = new Command( meta: new Metadata( creationTime: Carbon::createFromTimestamp(0), @@ -41,26 +50,34 @@ public function setUp(): void executePayload: new ExecutePayload( code: '(+ 2 2)' ) - ) + ), + networkId: 'testnet0', + nonce: 'test', + signers: new SignerCollection(new Signer( + publicKey: $keyPair->publicKey, + capabilities: new CapabilityCollection( + new Capability( + name: 'cap.example', + arguments: [] + ) + ) + )) ); - $command->setSigners(['public-key']); - - $signature = new Signature( - hash: 'hash', - signature: 'signature', - publicKey: 'public-key', + $this->signedCommand = new SignedCommand( + hash: 'test-hash', + signatures: new SignatureCollection(new Signature( + hash: 'test-hash', + signature: 'test-signature' + )), + command: $command ); - - $signatures = new SignatureCollection($signature); - - $this->signedCommand = new SignedCommand('hash', $signatures, $command); } /** @test */ public function it_should_send_a_signed_command_to_the_local_endpoint_and_return_a_response_object(): void { - $expectedRequestData = $this->signedCommand->toArray(); + $expectedRequestData = SignedCommandMapper::toArray($this->signedCommand); $expectedResponseData = [ 'gas' => 123, @@ -116,7 +133,7 @@ public function it_should_send_a_signed_command_to_the_local_endpoint_and_return /** @test */ public function it_should_send_a_collection_of_signed_commands_to_the_send_endpoint_and_return_a_collection_of_request_keys(): void { - $expectedRequestData = (new SignedCommandCollection($this->signedCommand))->toPayload(); + $expectedRequestData = SignedCommandCollectionMapper::toArray(new SignedCommandCollection($this->signedCommand)); $requestKeyString = 'y3aWL72-3wAy7vL9wcegGXnstH0lHi-q-cfxkhD5JCw'; diff --git a/tests/Unit/DataMappers/CommandMapperTest.php b/tests/Unit/DataMappers/CommandMapperTest.php new file mode 100644 index 0000000..a2a212a --- /dev/null +++ b/tests/Unit/DataMappers/CommandMapperTest.php @@ -0,0 +1,111 @@ +command = new Command( + meta: new Metadata( + creationTime: Carbon::createFromTimestamp(0), + ttl: 0, + gasLimit: 0, + chainId: '', + gasPrice: 0, + sender: '' + ), + payload: new Payload( + payloadType: PayloadType::EXECUTE, + executePayload: new ExecutePayload( + code: '(+ 2 2)' + ) + ), + networkId: 'testnet0', + nonce: 'test', + signers: new SignerCollection(new Signer( + publicKey: $keyPair->publicKey, + capabilities: new CapabilityCollection( + new Capability( + name: 'cap.example', + arguments: [] + ) + ) + )) + ); + + $this->commandArray = [ + 'networkId' => 'testnet0', + 'payload' => [ + 'exec' => [ + 'data' => [], + 'code' => '(+ 2 2)' + ] + ], + 'meta' => [ + 'creationTime' => 0, + 'ttl' => 0, + 'gasLimit' => 0, + 'chainId' => '', + 'gasPrice' => 0, + 'sender' => '' + ], + 'nonce' => 'test', + 'signers' => [ + [ + 'pubKey' => $keyPair->publicKey->toString(), + 'clist' => [ + [ + 'name' => 'cap.example', + 'args' => [] + ] + ] + ] + ], + ]; + } + /** @test */ + public function it_should_be_able_to_map_a_command_to_an_array(): void + { + $actual = CommandMapper::toArray($this->command); + + $this->assertEquals($this->commandArray, $actual); + } + + /** @test */ + public function it_should_be_able_to_map_a_command_to_a_string(): void + { + $actual = CommandMapper::toString($this->command); + + $this->assertEquals(json_encode($this->commandArray, JSON_THROW_ON_ERROR), $actual); + } + + /** @test */ + public function it_should_be_able_to_map_a_string_to_a_command(): void + { + $actual = CommandMapper::fromString(json_encode($this->commandArray, JSON_THROW_ON_ERROR)); + + $this->assertEquals($this->command, $actual); + } +} diff --git a/tests/Unit/DataMappers/SignedCommandCollectionMapperTest.php b/tests/Unit/DataMappers/SignedCommandCollectionMapperTest.php new file mode 100644 index 0000000..ed86bec --- /dev/null +++ b/tests/Unit/DataMappers/SignedCommandCollectionMapperTest.php @@ -0,0 +1,125 @@ +publicKey, + capabilities: new CapabilityCollection( + new Capability( + name: 'cap.example', + arguments: [] + ) + ) + )) + ); + + $commandArray = [ + 'networkId' => 'testnet0', + 'payload' => [ + 'exec' => [ + 'data' => [], + 'code' => '(+ 2 2)' + ] + ], + 'meta' => [ + 'creationTime' => 0, + 'ttl' => 0, + 'gasLimit' => 0, + 'chainId' => '', + 'gasPrice' => 0, + 'sender' => '' + ], + 'nonce' => 'test', + 'signers' => [ + [ + 'pubKey' => $keyPair->publicKey->toString(), + 'clist' => [ + [ + 'name' => 'cap.example', + 'args' => [] + ] + ] + ] + ], + ]; + + $this->signedCommand = new SignedCommand( + hash: 'test-hash', + signatures: new SignatureCollection(new Signature( + hash: 'test-hash', + signature: 'test-signature' + )), + command: $command + ); + + $this->signedCommandArray = [ + 'cmd' => json_encode($commandArray, JSON_THROW_ON_ERROR), + 'hash' => 'test-hash', + 'sigs' => [ + ['sig' => 'test-signature'] + ], + ]; + } + + /** @test */ + public function it_should_be_able_to_map_a_signed_command_collection_to_an_array(): void + { + $actual = SignedCommandCollectionMapper::toArray(new SignedCommandCollection($this->signedCommand)); + + $this->assertEquals(['cmds' => [$this->signedCommandArray]], $actual); + } + + /** @test */ + public function it_should_be_able_to_map_a_signed_command_collection_to_a_string(): void + { + $actual = SignedCommandCollectionMapper::toString(new SignedCommandCollection($this->signedCommand)); + + $this->assertEquals(json_encode(['cmds' => [$this->signedCommandArray]], JSON_THROW_ON_ERROR), $actual); + } +} diff --git a/tests/Unit/DataMappers/SignedCommandMapperTest.php b/tests/Unit/DataMappers/SignedCommandMapperTest.php new file mode 100644 index 0000000..76a80d8 --- /dev/null +++ b/tests/Unit/DataMappers/SignedCommandMapperTest.php @@ -0,0 +1,132 @@ +publicKey, + capabilities: new CapabilityCollection( + new Capability( + name: 'cap.example', + arguments: [] + ) + ) + )) + ); + + $commandArray = [ + 'networkId' => 'testnet0', + 'payload' => [ + 'exec' => [ + 'data' => [], + 'code' => '(+ 2 2)' + ] + ], + 'meta' => [ + 'creationTime' => 0, + 'ttl' => 0, + 'gasLimit' => 0, + 'chainId' => '', + 'gasPrice' => 0, + 'sender' => '' + ], + 'nonce' => 'test', + 'signers' => [ + [ + 'pubKey' => $keyPair->publicKey->toString(), + 'clist' => [ + [ + 'name' => 'cap.example', + 'args' => [] + ] + ] + ] + ], + ]; + + $this->signedCommand = new SignedCommand( + hash: 'test-hash', + signatures: new SignatureCollection(new Signature( + hash: 'test-hash', + signature: 'test-signature' + )), + command: $command + ); + + $this->signedCommandArray = [ + 'cmd' => json_encode($commandArray, JSON_THROW_ON_ERROR), + 'hash' => 'test-hash', + 'sigs' => [ + ['sig' => 'test-signature'] + ], + ]; + } + + /** @test */ + public function it_should_be_able_to_map_a_signed_command_to_an_array(): void + { + $actual = SignedCommandMapper::toArray($this->signedCommand); + + $this->assertEquals($this->signedCommandArray, $actual); + } + + /** @test */ + public function it_should_be_able_to_map_a_signed_command_to_a_string(): void + { + $actual = SignedCommandMapper::toString($this->signedCommand); + + $this->assertEquals(json_encode($this->signedCommandArray, JSON_THROW_ON_ERROR), $actual); + } + + /** @test */ + public function it_should_be_able_to_map_a_string_to_a_signed_command(): void + { + $actual = SignedCommandMapper::fromString(json_encode($this->signedCommandArray, JSON_THROW_ON_ERROR)); + + $this->assertEquals($this->signedCommand, $actual); + } +} diff --git a/tests/Unit/Pact/CommandFactoryTest.php b/tests/Unit/Pact/CommandFactoryTest.php new file mode 100644 index 0000000..3ad8d19 --- /dev/null +++ b/tests/Unit/Pact/CommandFactoryTest.php @@ -0,0 +1,126 @@ +keyPair = KeyFactory::generate(); + } + + /** @test */ + public function it_should_be_able_to_construct_a_command_with_default_options_from_a_execute_payload(): void + { + $factory = new CommandFactory(); + $payload = new ExecutePayload( + code: '(+ 2 2)' + ); + + $expected = new Command( + meta: (new MetadataFactory())->make(), + payload: new Payload( + payloadType: PayloadType::EXECUTE, + executePayload: $payload + ), + networkId: '0', + nonce: Carbon::now()->toISOString(), + signers: new SignerCollection(), + ); + + $actual = $factory->withExecutePayload($payload)->make(); + + $this->assertEquals($expected, $actual); + } + + /** @test */ + public function it_should_be_able_to_construct_a_command_with_default_options_from_a_continue_payload(): void + { + $factory = new CommandFactory(); + $payload = new ContinuePayload( + pactId: 'pact-id', + rollback: false, + step: 0, + proof: 'proof', + data: [] + ); + + $expected = new Command( + meta: (new MetadataFactory())->make(), + payload: new Payload( + payloadType: PayloadType::CONTINUE, + continuePayload: $payload + ), + networkId: '0', + nonce: Carbon::now()->toISOString(), + signers: new SignerCollection(), + ); + + $actual = $factory->withContinuePayload($payload)->make(); + + $this->assertEquals($expected, $actual); + } + + /** @test */ + public function it_should_be_able_to_construct_a_command_with_defined_options(): void + { + $factory = new CommandFactory(); + $payload = new ExecutePayload( + code: '(+ 2 2)' + ); + $signers = new SignerCollection( + new Signer( + publicKey: $this->keyPair->publicKey, + capabilities: new CapabilityCollection( + new Capability( + name: 'test.cap', + arguments: [] + ) + ) + ) + ); + $meta = (new MetadataFactory())->make(); + + $expected = new Command( + meta: $meta, + payload: new Payload( + payloadType: PayloadType::EXECUTE, + executePayload: $payload + ), + networkId: 'network-0', + nonce: 'test-nonce', + signers: $signers, + ); + + $actual = $factory->withExecutePayload($payload) + ->withNonce('test-nonce') + ->withSigners($signers) + ->withMetadata($meta) + ->withNetworkId('network-0') + ->make(); + + $this->assertEquals($expected, $actual); + } +} diff --git a/tests/Unit/Pact/CommandTest.php b/tests/Unit/Pact/CommandTest.php deleted file mode 100644 index a85381b..0000000 --- a/tests/Unit/Pact/CommandTest.php +++ /dev/null @@ -1,284 +0,0 @@ -assertEquals(Carbon::now()->toISOString(), $command->nonce); - } - - /** @test */ - public function it_should_cast_to_array(): void - { - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $expected = [ - 'signers' => [], - 'networkId' => null, - 'payload' => [ - 'exec' => [ - 'data' => [], - 'code' => '(+ 2 2)' - ] - ], - 'meta' => [ - 'creationTime' => 0, - 'ttl' => 0, - 'gasLimit' => 0, - 'chainId' => '', - 'gasPrice' => 0, - 'sender' => '' - ], - 'nonce' => '2021-11-26T12:30:00.000000Z' - ]; - - $this->assertEquals($expected, $command->toArray()); - } - - /** @test */ - public function it_should_cast_to_string(): void - { - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $expected = json_encode([ - 'signers' => [], - 'networkId' => null, - 'payload' => [ - 'exec' => [ - 'data' => [], - 'code' => '(+ 2 2)' - ] - ], - 'meta' => [ - 'creationTime' => 0, - 'ttl' => 0, - 'gasLimit' => 0, - 'chainId' => '', - 'gasPrice' => 0, - 'sender' => '' - ], - 'nonce' => '2021-11-26T12:30:00.000000Z' - ], JSON_THROW_ON_ERROR); - - $this->assertEquals($expected, $command->toString()); - } - - /** @test */ - public function it_should_construct_from_string(): void - { - $expected = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $jsonCommand = json_encode([ - 'signers' => [], - 'networkId' => null, - 'payload' => [ - 'exec' => [ - 'data' => [], - 'code' => '(+ 2 2)' - ] - ], - 'meta' => [ - 'creationTime' => 0, - 'ttl' => 0, - 'gasLimit' => 0, - 'chainId' => '', - 'gasPrice' => 0, - 'sender' => '' - ], - 'nonce' => '2021-11-26T12:30:00.000000Z' - ], JSON_THROW_ON_ERROR); - - $this->assertEquals($expected, Command::fromString($jsonCommand)); - } - - /** @test */ - public function it_should_be_able_to_set_signers(): void - { - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $expectedWithoutSigners = [ - 'signers' => [], - 'networkId' => null, - 'payload' => [ - 'exec' => [ - 'data' => [], - 'code' => '(+ 2 2)' - ] - ], - 'meta' => [ - 'creationTime' => 0, - 'ttl' => 0, - 'gasLimit' => 0, - 'chainId' => '', - 'gasPrice' => 0, - 'sender' => '' - ], - 'nonce' => '2021-11-26T12:30:00.000000Z' - ]; - - $this->assertEquals($expectedWithoutSigners, $command->toArray()); - - $expectedWithSigners = [ - 'signers' => [ - ['pubKey' => 'public-key1'], - ['pubKey' => 'public-key2'], - ], - 'networkId' => null, - 'payload' => [ - 'exec' => [ - 'data' => [], - 'code' => '(+ 2 2)' - ] - ], - 'meta' => [ - 'creationTime' => 0, - 'ttl' => 0, - 'gasLimit' => 0, - 'chainId' => '', - 'gasPrice' => 0, - 'sender' => '' - ], - 'nonce' => '2021-11-26T12:30:00.000000Z' - ]; - - $command->setSigners(['public-key1', 'public-key2']); - - $this->assertEquals($expectedWithSigners, $command->toArray()); - } - - /** @test */ - public function it_should_return_a_signed_command_object_when_using_get_signed_command(): void - { - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $keyPair = KeyPair::generate(); - - $command->setSigners([Hex::encode($keyPair->publicKey->getRawKeyMaterial())]); - - $message = $command->toString(); - $expectedHash = Base64UrlSafe::encodeUnpadded(Hash::generic($message)); - - $signedCommand = $command->getSignedCommand(new KeyPairCollection($keyPair)); - - $this->assertInstanceOf(SignedCommand::class, $signedCommand); - $this->assertSame($expectedHash, $signedCommand->hash); - $this->assertTrue(MessageSigner::verifySignature(Hash::generic($message), $signedCommand->signatures->first()->signature, $keyPair->publicKey)); - } -} diff --git a/tests/Unit/Pact/ContinuePayloadTest.php b/tests/Unit/Pact/ContinuePayloadTest.php deleted file mode 100644 index 8468308..0000000 --- a/tests/Unit/Pact/ContinuePayloadTest.php +++ /dev/null @@ -1,31 +0,0 @@ - 'value']; - - $continuePayload = new ContinuePayload($pactId, $rollback, $step, $proof, $data); - - $expectedArray = [ - 'proof' => $proof, - 'pactId' => $pactId, - 'rollback' => $rollback, - 'step' => $step, - 'data' => $data - ]; - - $this->assertSame($expectedArray, $continuePayload->toArray()); - } -} diff --git a/tests/Unit/Pact/ExecutePayloadTest.php b/tests/Unit/Pact/ExecutePayloadTest.php deleted file mode 100644 index 5cd7aff..0000000 --- a/tests/Unit/Pact/ExecutePayloadTest.php +++ /dev/null @@ -1,24 +0,0 @@ - 'value']; - - $executePayload = new ExecutePayload($code, $data); - $expectedArray = [ - 'data' => $data, - 'code' => $code, - ]; - - $this->assertSame($expectedArray, $executePayload->toArray()); - } -} diff --git a/tests/Unit/Pact/MetaTest.php b/tests/Unit/Pact/MetaTest.php deleted file mode 100644 index cc2d640..0000000 --- a/tests/Unit/Pact/MetaTest.php +++ /dev/null @@ -1,83 +0,0 @@ - $creationTime->getTimestamp(), - 'ttl' => $ttl, - 'gasLimit' => $gasLimit, - 'chainId' => $chainId, - 'gasPrice' => $gasPrice, - 'sender' => $sender - ]; - - $this->assertSame($expectedArray, $meta->toArray()); - } - - /** @test */ - public function it_should_be_able_to_be_constructed_from_an_options_array(): void - { - $creationTime = Carbon::now(); - $ttl = 300; - $gasLimit = 50; - $chainId = 'test-chain-id'; - $gasPrice = 1.25; - $sender = 'test-sender'; - - $expected = new Metadata($creationTime, $ttl, $gasLimit, $chainId, $gasPrice, $sender); - - $options = [ - 'creationTime' => $creationTime->getTimestamp(), - 'ttl' => $ttl, - 'gasLimit' => $gasLimit, - 'chainId' => $chainId, - 'gasPrice' => $gasPrice, - 'sender' => $sender - ]; - - $actual = Metadata::create($options); - - $this->assertEquals($expected, $actual); - } - - /** @test */ - public function it_should_be_able_to_be_constructed_using_the_create_method_with_default_options(): void - { - $creationTime = Carbon::now(); - $ttl = 7200; - $gasLimit = 10000; - $chainId = '0'; - $gasPrice = 1e-8; - $sender = ''; - - $expected = new Metadata($creationTime, $ttl, $gasLimit, $chainId, $gasPrice, $sender); - - $actual = Metadata::create(); - - $this->assertEquals($expected, $actual); - } -} diff --git a/tests/Unit/Pact/MetadataFactoryTest.php b/tests/Unit/Pact/MetadataFactoryTest.php new file mode 100644 index 0000000..1fcdee6 --- /dev/null +++ b/tests/Unit/Pact/MetadataFactoryTest.php @@ -0,0 +1,61 @@ +make(); + + $this->assertEquals($expected, $actual); + } + + /** @test */ + public function it_should_be_able_to_construct_a_metadata_object_with_supplied_options(): void + { + $expected = new Metadata( + creationTime: Carbon::now()->subDay(), + ttl: 3600, + gasLimit: 15000, + chainId: '2', + gasPrice: 1e-7, + sender: 'sender' + ); + + $actual = (new MetadataFactory()) + ->withOptions([ + 'creationTime' => Carbon::now()->subDay()->getTimestamp(), + 'ttl' => 3600, + 'gasLimit' => 15000, + 'chainId' => '2', + 'gasPrice' => 1e-7, + 'sender' => 'sender', + ]) + ->make(); + + $this->assertEquals($expected, $actual); + } +} diff --git a/tests/Unit/Pact/PayloadTest.php b/tests/Unit/Pact/PayloadTest.php deleted file mode 100644 index 213a799..0000000 --- a/tests/Unit/Pact/PayloadTest.php +++ /dev/null @@ -1,60 +0,0 @@ -expectException(InvalidArgumentException::class); - - new Payload(PayloadType::CONTINUE, null, new ExecutePayload('test', [])); - } - - /** @test */ - public function it_should_validate_execute_payload(): void - { - $this->expectException(InvalidArgumentException::class); - - new Payload(PayloadType::EXECUTE, new ContinuePayload('test', true, 1), null); - } - - /** @test */ - public function it_should_create_and_return_valid_execute_payload(): void - { - $payload = new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: 'test', - data: ['key' => 'value'] - ) - ); - - $this->assertSame([PayloadType::EXECUTE->value => ['data' => ['key' => 'value'], 'code' => 'test']], $payload->toArray()); - } - - /** @test */ - public function it_should_create_and_return_valid_continue_payload(): void - { - $payload = new Payload( - payloadType: PayloadType::CONTINUE, - continuePayload: new ContinuePayload( - pactId: 'test', - rollback: true, - step: 1, - proof: 'proof', - data: ['key' => 'value'] - ) - ); - - $this->assertSame([PayloadType::CONTINUE->value => ['proof' => 'proof', 'pactId' => 'test', 'rollback' => true, 'step' => 1, 'data' => ['key' => 'value']]], $payload->toArray()); - } -} diff --git a/tests/Unit/Pact/RequestKeyCollectionTest.php b/tests/Unit/Pact/RequestKeyCollectionTest.php deleted file mode 100644 index d32cbb5..0000000 --- a/tests/Unit/Pact/RequestKeyCollectionTest.php +++ /dev/null @@ -1,43 +0,0 @@ -assertEquals([$requestKey1, $requestKey2], $requestKeyCollection->toArray()); - } - - /** @test */ - public function it_should_get_first_request_key(): void - { - $requestKey1 = new RequestKey('key1'); - $requestKey2 = new RequestKey('key2'); - - $requestKeyCollection = new RequestKeyCollection($requestKey1, $requestKey2); - - $this->assertEquals($requestKey1, $requestKeyCollection->first()); - } - - /** @test */ - public function it_should_convert_to_plain_array(): void - { - $requestKey1 = new RequestKey('key1'); - $requestKey2 = new RequestKey('key2'); - - $requestKeyCollection = new RequestKeyCollection($requestKey1, $requestKey2); - - $this->assertEquals(['key1', 'key2'], $requestKeyCollection->toPlainArray()); - } -} diff --git a/tests/Unit/Pact/SignedCommandCollectionTest.php b/tests/Unit/Pact/SignedCommandCollectionTest.php deleted file mode 100644 index 8207ab3..0000000 --- a/tests/Unit/Pact/SignedCommandCollectionTest.php +++ /dev/null @@ -1,151 +0,0 @@ -setSigners(['public-key']); - - $signature = new Signature( - hash: 'hash', - signature: 'signature', - publicKey: 'public-key', - ); - - $signatures = new SignatureCollection($signature); - - $signedCommand = new SignedCommand('hash', $signatures, $command); - - $signedCommandCollection = new SignedCommandCollection($signedCommand); - - $this->assertEquals([$signedCommand], $signedCommandCollection->toArray()); - } - - /** @test */ - public function it_should_convert_in_to_a_payload_array(): void - { - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $command->setSigners(['public-key']); - - $signature = new Signature( - hash: 'hash', - signature: 'signature', - publicKey: 'public-key', - ); - - $signatures = new SignatureCollection($signature); - - $signedCommand = new SignedCommand('hash', $signatures, $command); - - $signedCommandCollection = new SignedCommandCollection($signedCommand); - - $expected = [ - 'cmds' => [ - [ - 'hash' => 'hash', - 'sigs' => [ - [ - 'sig' => 'signature' - ] - ], - 'cmd' => '{"signers":[{"pubKey":"public-key"}],"networkId":null,"payload":{"exec":{"data":[],"code":"(+ 2 2)"}},"meta":{"creationTime":0,"ttl":0,"gasLimit":0,"chainId":"","gasPrice":0,"sender":""},"nonce":"2021-11-26T12:30:00.000000Z"}' - ] - ] - ]; - - $this->assertEquals($expected, $signedCommandCollection->toPayload()); - } - - /** @test */ - public function it_should_convert_in_to_a_json_payload_string(): void - { - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $command->setSigners(['public-key']); - - $signature = new Signature( - hash: 'hash', - signature: 'signature', - publicKey: 'public-key', - ); - - $signatures = new SignatureCollection($signature); - - $signedCommand = new SignedCommand('hash', $signatures, $command); - - $signedCommandCollection = new SignedCommandCollection($signedCommand); - - $expected = '{"cmds":[{"hash":"hash","sigs":[{"sig":"signature"}],"cmd":"{\"signers\":[{\"pubKey\":\"public-key\"}],\"networkId\":null,\"payload\":{\"exec\":{\"data\":[],\"code\":\"(+ 2 2)\"}},\"meta\":{\"creationTime\":0,\"ttl\":0,\"gasLimit\":0,\"chainId\":\"\",\"gasPrice\":0,\"sender\":\"\"},\"nonce\":\"2021-11-26T12:30:00.000000Z\"}"}]}'; - $this->assertEquals($expected, $signedCommandCollection->toPayloadString()); - } -} diff --git a/tests/Unit/Pact/SignedCommandTest.php b/tests/Unit/Pact/SignedCommandTest.php deleted file mode 100644 index ae51a8f..0000000 --- a/tests/Unit/Pact/SignedCommandTest.php +++ /dev/null @@ -1,175 +0,0 @@ -setSigners(['public-key']); - - $signature = new Signature( - hash: 'hash', - signature: 'signature', - publicKey: 'public-key', - ); - - $signatures = new SignatureCollection($signature); - - $signedCommand = new SignedCommand('hash', $signatures, $command); - - $commandJson = $signedCommand->toString(); - - $signedCommandFromJson = SignedCommand::fromString($commandJson); - - $this->assertEquals($signedCommand->command, $signedCommandFromJson->command); - $this->assertEquals($signedCommand->hash, $signedCommandFromJson->hash); - } - - /** @test */ - public function it_should_validate_signatures_when_creating(): void - { - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $command->setSigners(['public-key']); - - $signature1 = new Signature( - hash: 'hash', - signature: 'signature', - publicKey: 'public-key', - ); - - $signature2 = new Signature( - hash: 'incorrect', - signature: 'signature', - publicKey: 'public-key', - ); - - $signatures = new SignatureCollection($signature1, $signature2); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Signatures for different hashes found: incorrect, expected: hash'); - - new SignedCommand('hash', $signatures, $command); - } - - /** @test */ - public function it_should_return_the_expected_array(): void - { - $signature1 = new Signature('hash1', 'signature1'); - $signature2 = new Signature('hash1', 'signature2'); - $signatureCollection = new SignatureCollection($signature1, $signature2); - - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $signedCommand = new SignedCommand('hash1', $signatureCollection, $command); - - $expectedArray = [ - 'hash' => 'hash1', - 'sigs' => [ - ['sig' => 'signature1'], - ['sig' => 'signature2'], - ], - 'cmd' => $command->toString(), - ]; - - $this->assertSame($expectedArray, $signedCommand->toArray()); - } - - /** @test */ - public function it_should_return_the_expected_string(): void - { - $signature1 = new Signature('hash1', 'signature1'); - $signature2 = new Signature('hash1', 'signature2'); - $signatureCollection = new SignatureCollection($signature1, $signature2); - - $command = new Command( - meta: new Metadata( - creationTime: Carbon::createFromTimestamp(0), - ttl: 0, - gasLimit: 0, - chainId: '', - gasPrice: 0, - sender: '' - ), - payload: new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 2 2)' - ) - ) - ); - - $signedCommand = new SignedCommand('hash1', $signatureCollection, $command); - - $expectedString = '{"hash":"hash1","sigs":[{"sig":"signature1"},{"sig":"signature2"}],"cmd":"{\"signers\":[],\"networkId\":null,\"payload\":{\"exec\":{\"data\":[],\"code\":\"(+ 2 2)\"}},\"meta\":{\"creationTime\":0,\"ttl\":0,\"gasLimit\":0,\"chainId\":\"\",\"gasPrice\":0,\"sender\":\"\"},\"nonce\":\"2021-11-26T12:30:00.000000Z\"}"}'; - $this->assertSame($expectedString, $signedCommand->toString()); - } -} diff --git a/tests/Unit/ValueObjects/HasCollectionMethodsTest.php b/tests/Unit/ValueObjects/HasCollectionMethodsTest.php new file mode 100644 index 0000000..e8ba7b0 --- /dev/null +++ b/tests/Unit/ValueObjects/HasCollectionMethodsTest.php @@ -0,0 +1,88 @@ +assertEquals($stringArray, $collection->toArray()); + } + + /** @test */ + public function it_should_be_able_to_return_the_first_element_of_a_collection(): void + { + $stringArray = [ + 'first', + 'second', + 'third', + 'fourth', + ]; + + $collection = new TestCollection(...$stringArray); + + $this->assertEquals('first', $collection->first()); + } + + /** @test */ + public function it_should_be_able_to_return_a_collection_element_at_a_given_key(): void + { + $stringArray = [ + 'first', + 'second', + 'third', + 'fourth', + ]; + + $collection = new TestCollection(...$stringArray); + + $this->assertEquals('third', $collection->get(2)); + } + + /** @test */ + public function it_should_be_able_to_return_the_int_count_of_the_amount_of_elements_in_the_collection(): void + { + $stringArray = [ + 'first', + 'second', + 'third', + 'fourth', + ]; + + $collection = new TestCollection(...$stringArray); + + $this->assertEquals(4, $collection->count()); + } +} + +final class TestCollection implements Collection +{ + use HasCollectionMethods; + + public function __construct(string ...$strings) + { + $this->array = $strings; + } +} From a5a913dfbaf281093d1c03ac11936bcb45a07686 Mon Sep 17 00:00:00 2001 From: Hergen Dillema Date: Mon, 9 Jan 2023 13:03:05 +0100 Subject: [PATCH 5/7] Updated readme, added interfaces --- readme.md | 107 ++++++++++-------- src/Contracts/Crypto/CommandSigner.php | 12 ++ src/Contracts/DataMappers/CommandMapper.php | 12 ++ .../SignedCommandCollectionMapper.php | 11 ++ .../DataMappers/SignedCommandMapper.php | 12 ++ src/Contracts/Pact/MetadataFactory.php | 11 ++ src/Crypto/CommandSigner.php | 3 +- src/DataMappers/CommandMapper.php | 3 +- .../SignedCommandCollectionMapper.php | 3 +- src/DataMappers/SignedCommandMapper.php | 3 +- src/Pact/MetadataFactory.php | 3 +- 11 files changed, 130 insertions(+), 50 deletions(-) create mode 100644 src/Contracts/Crypto/CommandSigner.php create mode 100644 src/Contracts/DataMappers/CommandMapper.php create mode 100644 src/Contracts/DataMappers/SignedCommandCollectionMapper.php create mode 100644 src/Contracts/DataMappers/SignedCommandMapper.php create mode 100644 src/Contracts/Pact/MetadataFactory.php diff --git a/readme.md b/readme.md index c0393a6..12240b3 100644 --- a/readme.md +++ b/readme.md @@ -26,22 +26,11 @@ composer require kadena-php/client Key Pairs are used to sign your Pact commands. You can generate a new KeyPair using ```php -$keyPair = \Kadena\ValueObjects\Signer\KeyPair::generate(); +$keyPair = KeyFactory::generate(); ``` -### Commands -Commands are requests sent to the Pact API. -They contain a payload, metadata, signers, network id, and a nonce. -When creating a command, only the metadata and payload are required. -The `networkId` is `null` by default, and when no nonce is set, it will default to the current time. - -Signers must be present in a signed command, but using the `getSignedCommand` will take care of that for you. - -First, let's create metadata to construct our command: - -```php -$metadata = Meta::create(); -``` -The `create()` method takes an optional array of options, options with their default values are: +### Metadata +Every command sent to the Kadena API requires a metadata object to be present. This object can be created manually, or be constructed using the `MetadataFactory`. +The factory will set predefined defaults if certain options are no provided. The defaults and options are as follows: ```php creationTime: Carbon::now(), ttl: 7200, @@ -50,49 +39,77 @@ chainId: '0', gasPrice: 1e-8, sender: '' ``` -Then create a payload: +If we want to create an object with the default options but on a different chain, we can do it like this: ```php -$executePayload = new Payload( - payloadType: PayloadType::EXECUTE, - executePayload: new ExecutePayload( - code: '(+ 1 2)' - ) -); +$factory = new MetadataFactory(); +$metadata = $factory->withOptions([ + 'chainId' => '1', +])->make(); +``` +If no custom options are required, you can just call `$factory->make()` to create your `Metadata` object. +### Signers +Commands have to be signed before sending them to the Kadena API. To support this, a `Signer` (one or many) has to be created. A signer consists of a public key and optionally a list of capabilities. -$continuePayload = new Payload( - payloadType: PayloadType::CONTINUE, - executePayload: new ContinuePayload( - pactId: 'pact-id', - rollback: false, - step: 0 - ) +Let's create a signer with a public key of `example-key` and the `coin.transfer` capability. As a signer can have multiple or no capabilities, all `Capability` objects should be wrapped in a `CapabilityCollection` object: +```php +// Just as an example, keys should be created using the KeyFactory +$publicKey = new PublicKey(new SignaturePublicKey(new HiddenString('example-key'))); + +$transferCapability = new Capability( + name: 'coin.transfer', + arguments: [ + 'address-from', + 'address-to', + 5 + ] ); + +$signer = new Signer( + publicKey: $publicKey, + capabilities: new CapabilityCollection($transferCapability) +) ``` -As you can see there are two payload types, an execute and a continue payload. With these metadata and payload classes, we can construct our commands: +Multiple signers can be wrapped in the `SignerCollection` object. + +### Payloads +Payloads are the code to be executed by pact. There are two types of payloads: and execute and a continue payload. ```php -$executeCommand = new Command( - meta: $metadata, - payload: $executePyaload +$executePayload new ExecutePayload( + code: '(+ 1 2)' ); -$continueCommand = new Command( - meta: $metadata, - payload: $continuePyaload +$continuePayload = new ContinuePayload( + pactId: 'pact-id', + rollback: false, + step: 0 ); ``` -### Signing Commands -After creating a command, you can sign it using any number of key pairs. To do this, first, create a `KeyPairCollection` from the key pairs you have. This can be a single key pair or many. +### Commands +Commands wrap all data sent to the Kadena API, a `Command` object can be created manually, but it is recommended to use the `CommandFactory` for this. +The factory will set certain defaults, and can be used like this: ```php -$kp1 = KeyPair::generate(); -$kp2 = KeyPair::generate(); +$factory = new CommandFactory(); -$keyPairCollection = new KeyPairCollection($kp1, $kp2); +$factory->withExecutePayload($executePayload) + ->withMetadata($metadata) + ->withSigners(new SignerCollection($signer)) + ->withNetworkId('mainnet0') + ->withNonce('nonce-string') + ->make(); +``` +The `withExecutePayload` or the `withContinuePayload` options are always required to create a `Command` object, but all others are optional. + +### Signing Commands +After creating a command, you can sign it using any number of key pairs. To do this, first, create a `KeyPairCollection` from the key pairs you have. +These key pairs should correspond to the signers you added to your account. +```php +$kpc = new KeyPairCollection($keypair); ``` Now using these key pairs, we can sign the previously created command ```php -$signedCommand = $command->getSignedCommand($keyPairCollection); +$signedCommand = CommandSigner::sign($command, $kpc); ``` This returns a new instance of a `SignedCommand` @@ -100,12 +117,12 @@ This returns a new instance of a `SignedCommand` Instead of signing the command in the backend, a command might be signed elsewhere (user wallet). A signed command can be reconstructed from a valid Pact command string using: ```php -$signedCommand = SignedCommand::fromString($commandString) +$signedCommand = SignedCommandMapper::fromString($commandString) ``` A signed command can also be cast to a string or an array using ```php -$signedCommand->toString(); -$signedCommand->toArray(); +$commandString = SignedCommandMapper::toString($signedCommand); +$commandArray = SignedCommandMapper::toArray($signedCommand); ``` ### Using the Client diff --git a/src/Contracts/Crypto/CommandSigner.php b/src/Contracts/Crypto/CommandSigner.php new file mode 100644 index 0000000..e66b2f9 --- /dev/null +++ b/src/Contracts/Crypto/CommandSigner.php @@ -0,0 +1,12 @@ + Date: Mon, 9 Jan 2023 13:03:23 +0100 Subject: [PATCH 6/7] codestyle --- src/Contracts/DataMappers/CommandMapper.php | 2 +- src/Contracts/DataMappers/SignedCommandCollectionMapper.php | 2 +- src/Contracts/DataMappers/SignedCommandMapper.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Contracts/DataMappers/CommandMapper.php b/src/Contracts/DataMappers/CommandMapper.php index 27b977b..ae6991d 100644 --- a/src/Contracts/DataMappers/CommandMapper.php +++ b/src/Contracts/DataMappers/CommandMapper.php @@ -9,4 +9,4 @@ interface CommandMapper public static function toArray(Command $command): array; public static function toString(Command $command): string; public static function fromString(string $commandJson): Command; -} \ No newline at end of file +} diff --git a/src/Contracts/DataMappers/SignedCommandCollectionMapper.php b/src/Contracts/DataMappers/SignedCommandCollectionMapper.php index 424f912..e1092b2 100644 --- a/src/Contracts/DataMappers/SignedCommandCollectionMapper.php +++ b/src/Contracts/DataMappers/SignedCommandCollectionMapper.php @@ -8,4 +8,4 @@ interface SignedCommandCollectionMapper { public static function toArray(SignedCommandCollection $commands): array; public static function toString(SignedCommandCollection $commands): string; -} \ No newline at end of file +} diff --git a/src/Contracts/DataMappers/SignedCommandMapper.php b/src/Contracts/DataMappers/SignedCommandMapper.php index 81abec8..c1a46f6 100644 --- a/src/Contracts/DataMappers/SignedCommandMapper.php +++ b/src/Contracts/DataMappers/SignedCommandMapper.php @@ -9,4 +9,4 @@ interface SignedCommandMapper public static function toArray(SignedCommand $command): array; public static function toString(SignedCommand $command): string; public static function fromString(string $commandJson): SignedCommand; -} \ No newline at end of file +} From 06df73063da4b44493b5f6a339aa95e8da6a4940 Mon Sep 17 00:00:00 2001 From: Hergen Dillema Date: Mon, 9 Jan 2023 13:04:46 +0100 Subject: [PATCH 7/7] Readme update --- readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 12240b3..c201704 100644 --- a/readme.md +++ b/readme.md @@ -9,9 +9,7 @@ Using this package allows you to call things like admin functions in Pact from y > If your users have to sign a command, something like Kadena.js would still be required on the frontend. This is not a complete replacement. -> ⚠️ This package is under active development and has only been released under versions 0.x.x to allow for testing of the Package. During development, breaking changes might be made in minor version upgrades. -> -> Some features like adding capabilities are still missing. +> ⚠️ This package is under active development and has no stable production release yet ## Installation