mProtoTransaction = $base64Data; $this->mTransactionBody = new TransactionBody($this->mProtoTransaction->getBodyBytes()); return; } try { $transactionBin = sodium_base642bin($base64Data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); } catch(\SodiumException $e) { //$this->addError('Transaction', $e->getMessage());// . ' ' . $base64Data); //return; $transactionBin = base64_decode($base64Data, true); if($transactionBin == false) { $this->addError('Transaction', $e->getMessage());// . ' ' . $base64Data); $this->addError('base64', $base64Data); return; } } //*/} if($transactionBin == false) { //$this->addError('base64 decode failed'); $this->addError('Transaction', 'base64 decode error: ' . $base64Data); } else { //var_dump($transactionBin); $this->mProtoTransaction = new \Proto\Gradido\GradidoTransaction(); try { $this->mProtoTransaction->mergeFromString($transactionBin); //var_dump($this->mProtoTransaction); // cannot catch Exception with cakePHP, I don't know why } catch(\Google\Protobuf\Internal\GPBDecodeException $e) { //var_dump($e); $this->addError('Transaction', $e->getMessage()); return; }//*/ //echo 'serialize to json:
'; //echo $this->mProtoTransaction->serializeToJsonString(); //echo "body bytes:
"; //var_dump($this->mProtoTransaction->getBodyBytes()); //echo "
end body bytes
"; $this->mTransactionBody = new TransactionBody($this->mProtoTransaction->getBodyBytes()); } } static public function build(\Proto\Gradido\TransactionBody $transactionBody, $senderKeyPair) { $protoTransaction = new \Proto\Gradido\GradidoTransaction(); $recevied = new \Proto\Gradido\TimestampSeconds(); $recevied->setSeconds(time()); $protoTransaction->setReceived($recevied); $bodyBytes = $transactionBody->serializeToString(); $sigMap = SignatureMap::build($bodyBytes, [$senderKeyPair]); $protoTransaction->setSigMap($sigMap->getProto()); $protoTransaction->setBodyBytes($bodyBytes); return $protoTransaction; } public function getTransactionBody() { return $this->mTransactionBody; } public function getFirstPublic() { if(!$this->mProtoTransaction || !$this->mProtoTransaction->getSigMap()) { return ''; } $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair(); return $sigPairs[0]->getPubKey(); } public function getFirstSigningUser() { return $this->getStateUserFromPublickey($this->getFirstPublic()); } public function getId() { return $this->mProtoTransaction->getId(); } public function validate() { $sigMap = $this->mProtoTransaction->getSigMap(); if(!$sigMap) { $this->addError('Transaction', 'signature map is zero'); //var_dump($this->mProtoTransaction); return false; } //var_dump($sigMap); //die(); $sigPairs = $sigMap->getSigPair(); $bodyBytes = $this->mProtoTransaction->getBodyBytes(); if(!$sigPairs || count($sigPairs) < 1) { $this->addError('Transaction::validate', 'no signature found'); return false; } // check signature(s) foreach($sigPairs as $sigPair) { //echo 'sig Pair: '; var_dump($sigPair); echo "
"; $pubkey = $sigPair->getPubKey(); $signature = $sigPair->getEd25519(); //echo "verify bodybytes:
" . bin2hex($bodyBytes) . '
'; if (!\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) { $this->addError('Transaction::validate', 'signature for key ' . bin2hex($pubkey) . ' isn\'t valid ' ); return false; } } if(!$this->mTransactionBody->validate($sigPairs)) { $this->addErrors($this->mTransactionBody->getErrors()); return false; } return true; } public function save() { $connection = ConnectionManager::get('default'); $connection->begin(); //id transaction_id signature pubkey if (!$this->mTransactionBody->save($this->getFirstPublic(), $this->mProtoTransaction->getSigMap())) { $this->addErrors($this->mTransactionBody->getErrors()); $connection->rollback(); // correct auto-increment value to prevent gaps $transactionsTable = $this->getTable('transactions'); $transactions = $transactionsTable->find()->select(['id'])->contain(false); $count = $transactions->count(); $connection = ConnectionManager::get('default'); $connection->execute("ALTER TABLE `transactions` auto_increment = $count;"); return false; } // save transaction signatures $transactionsSignaturesTable = TableRegistry::getTableLocator()->get('transaction_signatures'); $transactionId = $this->mTransactionBody->getTransactionID(); //signature pubkey $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair(); //echo "sigPairs: "; var_dump($sigPairs); $signatureEntitys = []; foreach($sigPairs as $sigPair) { $signatureEntity = $transactionsSignaturesTable->newEntity(); $signatureEntity->transaction_id = $transactionId; $signatureEntity->signature = $sigPair->getEd25519(); $signatureEntity->pubkey = $sigPair->getPubKey(); array_push($signatureEntitys, $signatureEntity); } //debug($signatureEntitys); if(!$transactionsSignaturesTable->saveMany($signatureEntitys)) { foreach($signatureEntitys as $entity) { $errors = $entity->getErrors(); if(!$errors && count($errors) > 0) { $pubkeyHex = bin2hex($entity->pubkey); $this->addError('Transaction::save', 'error saving signature for pubkey: ' . $pubkeyHex . ', with errors: ' . json_encode($errors) ); } } $connection->rollback(); return false; } $connection->commit(); $specificTransaction = $this->mTransactionBody->getSpecificTransaction(); $specificTransaction->sendNotificationEmail($this->mTransactionBody->getMemo()); $this->addWarnings($specificTransaction->getWarnings()); return true; } static public function fromTable($id) { $transactionsTable = TableRegistry::getTableLocator()->get('transactions'); $transactionEntry = $transactionsTable ->find('all') ->where(['id' => $id]) ->contain([ 'TransactionCreations', 'TransactionSendCoins', 'TransactionSignatures']) ->first(); //var_dump($transactionEntry->toArray()); $protoTransaction = new \Proto\Gradido\Transaction(); $protoTransaction->setId($transactionEntry->id); $recevied = new \Proto\Gradido\TimestampSeconds(); $recevied->setSeconds($transactionEntry->received->getTimestamp()); $protoTransaction->setReceived($recevied); $sigMap = SignatureMap::fromEntity($transactionEntry->transaction_signatures); $protoTransaction->setSigMap($sigMap->getProto()); //echo "sig map: check
"; $protoTransaction->setTxHash(stream_get_contents($transactionEntry->tx_hash)); $body = TransactionBody::fromEntity($transactionEntry->memo, $transactionEntry); if(is_array($body)) { return ['state' => 'error', 'msg' => 'error creating body transaction', 'details' => $body]; } // validate signatures $sigPairs = $sigMap->getProto()->getSigPair(); if(!$sigPairs || count($sigPairs) < 1) { return ['state' => 'error', 'msg' => 'error no signatures found']; } //echo "verify bodybytes:
" . bin2hex($bodyBytes) . '
'; $created = new \Proto\Gradido\TimestampSeconds(); $created->setSeconds($recevied->getSeconds()); $body->setCreated($created); $bodyBytes = $body->serializeToString(); $createTrys = 0; $createRight = false; // check signature(s) and // try to get created field of TransactionBody right, because it wasn't saved foreach($sigPairs as $sigPair) { //echo 'sig Pair: '; var_dump($sigPair); echo "
"; $pubkey = $sigPair->getPubKey(); $signature = $sigPair->getEd25519(); if(!$createRight) { while($createTrys < 500) { if(\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) { $createRight = true; break; } else { $createTrys++; $created->setSeconds($created->getSeconds() - 1); //$body->setCreated($created); $bodyBytes = $body->serializeToString(); } } } if (!\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) { return ['state' => 'error', 'msg' => 'signature for key ' . bin2hex($pubkey) . ' isn\'t valid ']; } } $protoTransaction->setBodyBytes($bodyBytes); return $protoTransaction; } }