mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
289 lines
10 KiB
PHP
289 lines
10 KiB
PHP
<?php
|
|
|
|
/*
|
|
* To change this license header, choose License Headers in Project Properties.
|
|
* To change this template file, choose Tools | Templates
|
|
* and open the template in the editor.
|
|
*/
|
|
|
|
namespace Model\Transactions;
|
|
|
|
//use Model\Messages\Gradido\Transaction;
|
|
//use Model\Messages\Gradido\TransactionBody;
|
|
use Cake\ORM\TableRegistry;
|
|
use Cake\Datasource\ConnectionManager;
|
|
|
|
class Transaction extends TransactionBase {
|
|
|
|
private $mProtoTransaction = null;
|
|
private $mTransactionBody = null;
|
|
|
|
|
|
|
|
public function __construct($base64Data)
|
|
{
|
|
//$transactionBin = base64_decode($base64Data, true);
|
|
//if($transactionBin == false) {
|
|
//sodium_base64_VARIANT_URLSAFE_NO_PADDING
|
|
if(is_a($base64Data, '\Proto\Gradido\GradidoTransaction')) {
|
|
$this->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: <br>';
|
|
//echo $this->mProtoTransaction->serializeToJsonString();
|
|
//echo "body bytes: <br>";
|
|
//var_dump($this->mProtoTransaction->getBodyBytes());
|
|
//echo "<br>end body bytes<br>";
|
|
$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 "<br>";
|
|
$pubkey = $sigPair->getPubKey();
|
|
$signature = $sigPair->getEd25519();
|
|
//echo "verify bodybytes: <br>" . bin2hex($bodyBytes) . '<br>';
|
|
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<br>";
|
|
$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: <br>" . bin2hex($bodyBytes) . '<br>';
|
|
$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 "<br>";
|
|
$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;
|
|
}
|
|
|
|
} |