add lint rule for sorted import like in backend, sort imports, refactor enums, remove KeyManager

This commit is contained in:
einhorn_b 2023-12-19 15:07:53 +01:00
parent 7add77903a
commit 0567a3ddf0
63 changed files with 487 additions and 524 deletions

View File

@ -77,30 +77,30 @@ module.exports = {
// 'import/no-named-default': 'error',
// 'import/no-namespace': 'error',
// 'import/no-unassigned-import': 'error',
// 'import/order': [
// 'error',
// {
// groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
// 'newlines-between': 'always',
// pathGroups: [
// {
// pattern: '@?*/**',
// group: 'external',
// position: 'after',
// },
// {
// pattern: '@/**',
// group: 'external',
// position: 'after',
// },
// ],
// alphabetize: {
// order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
// caseInsensitive: true /* ignore case. Options: [true, false] */,
// },
// distinctGroup: true,
// },
// ],
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
pathGroups: [
{
pattern: '@?*/**',
group: 'external',
position: 'after',
},
{
pattern: '@/**',
group: 'external',
position: 'after',
},
],
alphabetize: {
order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
caseInsensitive: true /* ignore case. Options: [true, false] */,
},
distinctGroup: true,
},
],
// 'import/prefer-default-export': 'off',
// n
'n/handle-callback-err': 'error',

View File

@ -1,10 +1,10 @@
import { KeyManager } from '@/manager/KeyManager'
import { KeyPair } from '@/data/KeyPair'
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
import { hardenDerivationIndex } from '@/utils/derivationHelper'
import { Account } from '@entity/Account'
import Decimal from 'decimal.js-light'
import { KeyPair } from '@/data/KeyPair'
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
import { hardenDerivationIndex } from '@/utils/derivationHelper'
import { accountTypeToAddressType } from '@/utils/typeConverter'
const GMW_ACCOUNT_DERIVATION_INDEX = 1
@ -15,14 +15,11 @@ export class AccountFactory {
createdAt: Date,
derivationIndex: number,
type: AddressType,
parentKeyPair?: KeyPair,
parentKeyPair: KeyPair,
): Account {
const account = Account.create()
account.derivationIndex = derivationIndex
account.derive2Pubkey = KeyManager.getInstance().derive(
[derivationIndex],
parentKeyPair,
).publicKey
account.derive2Pubkey = parentKeyPair.derive([derivationIndex]).publicKey
account.type = type.valueOf()
account.createdAt = createdAt
account.balanceConfirmedAt = new Decimal(0)
@ -33,7 +30,7 @@ export class AccountFactory {
public static createAccountFromUserAccountDraft(
{ createdAt, accountType, user }: UserAccountDraft,
parentKeyPair?: KeyPair,
parentKeyPair: KeyPair,
): Account {
return AccountFactory.createAccount(
new Date(createdAt),

View File

@ -1,9 +1,10 @@
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { getDataSource } from '@/typeorm/DataSource'
import { Account } from '@entity/Account'
import { User } from '@entity/User'
import { In } from 'typeorm'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { getDataSource } from '@/typeorm/DataSource'
export const AccountRepository = getDataSource()
.getRepository(Account)
.extend({

View File

@ -1,13 +1,17 @@
import 'reflect-metadata'
import { TestDB } from '@test/TestDB'
import { AccountFactory } from './Account.factory'
import { AddressType } from './proto/3_3/enum/AddressType'
import { generateKeyPair, generateMnemonic } from '@/utils/cryptoHelper'
import { Decimal } from 'decimal.js-light'
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
import { TestDB } from '@test/TestDB'
import { AccountType } from '@/graphql/enum/AccountType'
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { AccountFactory } from './Account.factory'
import { AccountRepository } from './Account.repository'
import { KeyPair } from './KeyPair'
import { Mnemonic } from './Mnemonic'
import { AddressType } from './proto/3_3/enum/AddressType'
import { UserFactory } from './User.factory'
import { UserLogic } from './User.logic'
@ -19,9 +23,9 @@ jest.mock('@typeorm/DataSource', () => ({
describe('data/Account test factory and repository', () => {
const now = new Date()
const keyPair1 = generateKeyPair(generateMnemonic('62ef251edc2416f162cd24ab1711982b'))
const keyPair2 = generateKeyPair(generateMnemonic('000a0000000002000000000003000070'))
const keyPair3 = generateKeyPair(generateMnemonic('00ba541a1000020000000000300bda70'))
const keyPair1 = new KeyPair(new Mnemonic('62ef251edc2416f162cd24ab1711982b'))
const keyPair2 = new KeyPair(new Mnemonic('000a0000000002000000000003000070'))
const keyPair3 = new KeyPair(new Mnemonic('00ba541a1000020000000000300bda70'))
const userGradidoID = '6be949ab-8198-4acf-ba63-740089081d61'
describe('test factory methods', () => {
@ -36,6 +40,10 @@ describe('data/Account test factory and repository', () => {
const account = AccountFactory.createAccount(now, 1, AddressType.COMMUNITY_HUMAN, keyPair1)
expect(account).toMatchObject({
derivationIndex: 1,
derive2Pubkey: Buffer.from(
'cb88043ef4833afc01d6ed9b34e1aa48e79dce5ff97c07090c6600ec05f6d994',
'hex',
),
type: AddressType.COMMUNITY_HUMAN,
createdAt: now,
balanceCreatedAtDate: now,
@ -53,6 +61,10 @@ describe('data/Account test factory and repository', () => {
const account = AccountFactory.createAccountFromUserAccountDraft(userAccountDraft, keyPair1)
expect(account).toMatchObject({
derivationIndex: 1,
derive2Pubkey: Buffer.from(
'cb88043ef4833afc01d6ed9b34e1aa48e79dce5ff97c07090c6600ec05f6d994',
'hex',
),
type: AddressType.COMMUNITY_HUMAN,
createdAt: now,
balanceCreatedAtDate: now,
@ -65,6 +77,10 @@ describe('data/Account test factory and repository', () => {
const account = AccountFactory.createGmwAccount(keyPair1, now)
expect(account).toMatchObject({
derivationIndex: 2147483649,
derive2Pubkey: Buffer.from(
'05f0060357bb73bd290283870fc47a10b3764f02ca26938479ed853f46145366',
'hex',
),
type: AddressType.COMMUNITY_GMW,
createdAt: now,
balanceCreatedAtDate: now,
@ -77,6 +93,10 @@ describe('data/Account test factory and repository', () => {
const account = AccountFactory.createAufAccount(keyPair1, now)
expect(account).toMatchObject({
derivationIndex: 2147483650,
derive2Pubkey: Buffer.from(
'6c749f8693a4a58c948e5ae54df11e2db33d2f98673b56e0cf19c0132614ab59',
'hex',
),
type: AddressType.COMMUNITY_AUF,
createdAt: now,
balanceCreatedAtDate: now,
@ -89,7 +109,6 @@ describe('data/Account test factory and repository', () => {
describe('test repository functions', () => {
beforeAll(async () => {
await con.setupTestDB()
await Promise.all([
AccountFactory.createAufAccount(keyPair1, now).save(),
AccountFactory.createGmwAccount(keyPair1, now).save(),

View File

@ -1,14 +1,16 @@
import { Community } from '@entity/Community'
import { FindOptionsSelect, In, IsNull, Not } from 'typeorm'
import { CommunityArg } from '@/graphql/arg/CommunityArg'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { TransactionError } from '@/graphql/model/TransactionError'
import { LogError } from '@/server/LogError'
import { getDataSource } from '@/typeorm/DataSource'
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
import { Community } from '@entity/Community'
import { FindOptionsSelect, In, IsNull, Not } from 'typeorm'
import { KeyPair } from './KeyPair'
import { LogError } from '@/server/LogError'
export const CommunityRepository = getDataSource()
.getRepository(Community)

View File

@ -1,38 +1,87 @@
// https://www.npmjs.com/package/bip32-ed25519?activeTab=code
import { toPublic } from 'bip32-ed25519'
import { Community } from '@entity/Community'
// https://www.npmjs.com/package/bip32-ed25519
import { LogError } from '@/server/LogError'
import { toPublic, derivePrivate, sign, verify, generateFromSeed } from 'bip32-ed25519'
import { Mnemonic } from './Mnemonic'
/**
* Class Managing Key Pair and also generate, sign and verify signature with it
*/
export class KeyPair {
private _publicKey: Buffer
private _chainCode: Buffer
private _privateKey: Buffer
/**
* @param input: Buffer = extended private key, returned from bip32-ed25519 generateFromSeed
* @param input: Mnemonic = Mnemonic or Passphrase which work as seed for generating algorithms
* @param input: Buffer = extended private key, returned from bip32-ed25519 generateFromSeed or from derivePrivate
* @param input: Community = community entity with keys loaded from db
*
*/
public constructor(input: Buffer | Community) {
if (input instanceof Buffer) {
this.privateKey = input.subarray(0, 64)
this.chainCode = input.subarray(64, 96)
this.publicKey = toPublic(input).subarray(0, 32)
public constructor(input: Mnemonic | Buffer | Community) {
if (input instanceof Mnemonic) {
this.loadFromExtendedPrivateKey(generateFromSeed(input.seed))
} else if (input instanceof Buffer) {
this.loadFromExtendedPrivateKey(input)
} else if (input instanceof Community) {
if (!input.rootPrivkey || !input.rootChaincode || !input.rootPubkey) {
throw new LogError('missing private key or chaincode or public key in commmunity entity')
}
this.privateKey = input.rootPrivkey
this.publicKey = input.rootPubkey
this.chainCode = input.rootChaincode
this._privateKey = input.rootPrivkey
this._publicKey = input.rootPubkey
this._chainCode = input.rootChaincode
}
}
/**
* copy keys to community entity
* @param community
*/
public fillInCommunityKeys(community: Community) {
community.rootPubkey = this._publicKey
community.rootPrivkey = this._privateKey
community.rootChaincode = this._chainCode
}
private loadFromExtendedPrivateKey(extendedPrivateKey: Buffer) {
if (extendedPrivateKey.length !== 96) {
throw new LogError('invalid extended private key')
}
this._privateKey = extendedPrivateKey.subarray(0, 64)
this._chainCode = extendedPrivateKey.subarray(64, 96)
this._publicKey = toPublic(extendedPrivateKey).subarray(0, 32)
}
public getExtendPrivateKey(): Buffer {
return Buffer.concat([this.privateKey, this.chainCode])
return Buffer.concat([this._privateKey, this._chainCode])
}
public getExtendPublicKey(): Buffer {
return Buffer.concat([this.publicKey, this.chainCode])
return Buffer.concat([this._publicKey, this._chainCode])
}
publicKey: Buffer
chainCode: Buffer
privateKey: Buffer
public get publicKey(): Buffer {
return this._publicKey
}
public derive(path: number[]): KeyPair {
const extendedPrivateKey = this.getExtendPrivateKey()
return new KeyPair(
path.reduce(
(extendPrivateKey: Buffer, node: number) => derivePrivate(extendPrivateKey, node),
extendedPrivateKey,
),
)
}
public sign(message: Buffer): Buffer {
return sign(message, this.getExtendPrivateKey())
}
public verify(message: Buffer, signature: Buffer): boolean {
return verify(message, signature, this.getExtendPublicKey())
}
}

View File

@ -0,0 +1,25 @@
// https://www.npmjs.com/package/bip39
import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39'
// eslint-disable-next-line camelcase
import { randombytes_buf } from 'sodium-native'
export class Mnemonic {
private _passphrase = ''
public constructor(seed?: Buffer | string) {
if (seed) {
this._passphrase = entropyToMnemonic(seed)
return
}
const entropy = Buffer.alloc(256)
randombytes_buf(entropy)
this._passphrase = entropyToMnemonic(entropy)
}
public get passphrase(): string {
return this._passphrase
}
public get seed(): Buffer {
return mnemonicToSeedSync(this._passphrase)
}
}

View File

@ -1,13 +1,15 @@
import { GradidoTransaction } from '@/data/proto/3_3/GradidoTransaction'
import { TransactionBody } from '@/data/proto/3_3/TransactionBody'
import { bodyBytesToTransactionBody, transactionBodyToBodyBytes } from '@/utils/typeConverter'
import { Transaction } from '@entity/Transaction'
import { AccountRepository } from './Account.repository'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { CommunityRepository } from './Community.repository'
import { LogError } from '@/server/LogError'
import { Account } from '@entity/Account'
import { Community } from '@entity/Community'
import { Transaction } from '@entity/Transaction'
import { GradidoTransaction } from '@/data/proto/3_3/GradidoTransaction'
import { TransactionBody } from '@/data/proto/3_3/TransactionBody'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { LogError } from '@/server/LogError'
import { bodyBytesToTransactionBody, transactionBodyToBodyBytes } from '@/utils/typeConverter'
import { AccountRepository } from './Account.repository'
import { CommunityRepository } from './Community.repository'
import { TransactionBodyBuilder } from './proto/TransactionBody.builder'
export class TransactionBuilder {

View File

@ -1,7 +1,8 @@
import { getDataSource } from '@/typeorm/DataSource'
import { Transaction } from '@entity/Transaction'
import { IsNull } from 'typeorm'
import { getDataSource } from '@/typeorm/DataSource'
// https://www.artima.com/articles/the-dci-architecture-a-new-vision-of-object-oriented-programming
export const TransactionRepository = getDataSource()
.getRepository(Transaction)

View File

@ -1,10 +1,12 @@
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
import { User } from '@entity/User'
import { UserLogic } from './User.logic'
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
import { KeyPair } from './KeyPair'
import { UserLogic } from './User.logic'
export class UserFactory {
static create(userAccountDraft: UserAccountDraft, parentKeys?: KeyPair): User {
static create(userAccountDraft: UserAccountDraft, parentKeys: KeyPair): User {
const user = User.create()
user.createdAt = new Date(userAccountDraft.createdAt)
user.gradidoID = userAccountDraft.user.uuid

View File

@ -1,9 +1,10 @@
import { User } from '@entity/User'
import { KeyPair } from './KeyPair'
import { LogError } from '@/server/LogError'
import { uuid4ToBuffer } from '@/utils/typeConverter'
import { hardenDerivationIndex } from '@/utils/derivationHelper'
import { KeyManager } from '@/manager/KeyManager'
import { uuid4ToBuffer } from '@/utils/typeConverter'
import { KeyPair } from './KeyPair'
export class UserLogic {
// eslint-disable-next-line no-useless-constructor
@ -15,7 +16,7 @@ export class UserLogic {
* @returns
*/
calculateKeyPair = (parentKeys?: KeyPair): KeyPair => {
calculateKeyPair = (parentKeys: KeyPair): KeyPair => {
if (!this.user.gradidoID) {
throw new LogError('missing GradidoID for user.', { id: this.user.id })
}
@ -27,7 +28,7 @@ export class UserLogic {
parts[i] = hardenDerivationIndex(wholeHex.subarray(i * 4, (i + 1) * 4).readUInt32BE())
}
// parts: [2206563009, 2629978174, 2324817329, 2405141782]
const keyPair = KeyManager.getInstance().derive(parts, parentKeys)
const keyPair = parentKeys.derive(parts)
if (this.user.derive1Pubkey && this.user.derive1Pubkey.compare(keyPair.publicKey) !== 0) {
throw new LogError(
'The freshly derived public key does not correspond to the stored public key',

View File

@ -1,8 +1,9 @@
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { getDataSource } from '@/typeorm/DataSource'
import { Account } from '@entity/Account'
import { User } from '@entity/User'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { getDataSource } from '@/typeorm/DataSource'
export const UserRepository = getDataSource()
.getRepository(User)
.extend({

View File

@ -1,8 +1,8 @@
import { AbstractTransaction } from '../AbstractTransaction'
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
import { Field, Message } from 'protobufjs'
import { Community } from '@entity/Community'
import { Transaction } from '@entity/Transaction'
import { Field, Message } from 'protobufjs'
import { AbstractTransaction } from '../AbstractTransaction'
// https://www.npmjs.com/package/@apollo/protobufjs
// eslint-disable-next-line no-use-before-define
@ -30,11 +30,6 @@ export class CommunityRoot extends Message<CommunityRoot> implements AbstractTra
@Field.d(3, 'bytes')
public aufPubkey: Buffer
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public validate(_level: TransactionValidationLevel): boolean {
throw new Error('Method not implemented.')
}
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
public fillTransactionRecipe(recipe: Transaction): void {}
}

View File

@ -1,7 +1,9 @@
import { Field, Message } from 'protobufjs'
import { base64ToBuffer } from '@/utils/typeConverter'
import { GradidoTransaction } from './GradidoTransaction'
import { TimestampSeconds } from './TimestampSeconds'
import { base64ToBuffer } from '@/utils/typeConverter'
/*
id will be set by Node server

View File

@ -1,9 +1,11 @@
import 'reflect-metadata'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { GradidoCreation } from './GradidoCreation'
import { TransactionError } from '@/graphql/model/TransactionError'
import { TransactionErrorType } from '@enum/TransactionErrorType'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { GradidoCreation } from './GradidoCreation'
describe('proto/3.3/GradidoCreation', () => {
it('test with missing targetDate', () => {
const transactionDraft = new TransactionDraft()

View File

@ -1,15 +1,16 @@
import { Account } from '@entity/Account'
import { Transaction } from '@entity/Transaction'
import { Decimal } from 'decimal.js-light'
import { Field, Message } from 'protobufjs'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { AbstractTransaction } from '../AbstractTransaction'
import { TimestampSeconds } from './TimestampSeconds'
import { TransferAmount } from './TransferAmount'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { AbstractTransaction } from '../AbstractTransaction'
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
import { Transaction } from '@entity/Transaction'
import Decimal from 'decimal.js-light'
import { Account } from '@entity/Account'
// need signature from group admin or
// percent of group users another than the receiver
@ -36,17 +37,16 @@ export class GradidoCreation extends Message<GradidoCreation> implements Abstrac
}
}
// recipient: TransferAmount contain
// - recipient public key
// - amount
// - communityId // only set if not the same as recipient community
@Field.d(1, TransferAmount)
public recipient: TransferAmount
@Field.d(3, 'TimestampSeconds')
public targetDate: TimestampSeconds
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public validate(level: TransactionValidationLevel): boolean {
throw new Error('Method not implemented.')
}
public fillTransactionRecipe(recipe: Transaction): void {
recipe.amount = new Decimal(this.recipient.amount ?? 0)
}

View File

@ -1,11 +1,11 @@
import { Transaction } from '@entity/Transaction'
import Decimal from 'decimal.js-light'
import { Field, Message } from 'protobufjs'
import { AbstractTransaction } from '../AbstractTransaction'
import { GradidoTransfer } from './GradidoTransfer'
import { TimestampSeconds } from './TimestampSeconds'
import { AbstractTransaction } from '../AbstractTransaction'
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
import { Transaction } from '@entity/Transaction'
import Decimal from 'decimal.js-light'
// transaction type for chargeable transactions
// for transaction for people which haven't a account already
@ -36,11 +36,6 @@ export class GradidoDeferredTransfer
// split for n recipient
// max gradido per recipient? or per transaction with cool down?
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public validate(level: TransactionValidationLevel): boolean {
throw new Error('Method not implemented.')
}
public fillTransactionRecipe(recipe: Transaction): void {
recipe.amount = new Decimal(this.transfer.sender.amount ?? 0)
}

View File

@ -1,10 +1,11 @@
import { Field, Message } from 'protobufjs'
import { SignatureMap } from './SignatureMap'
import { TransactionBody } from './TransactionBody'
import { SignaturePair } from './SignaturePair'
import { LogError } from '@/server/LogError'
import { SignatureMap } from './SignatureMap'
import { SignaturePair } from './SignaturePair'
import { TransactionBody } from './TransactionBody'
// https://www.npmjs.com/package/@apollo/protobufjs
// eslint-disable-next-line no-use-before-define
export class GradidoTransaction extends Message<GradidoTransaction> {

View File

@ -1,12 +1,13 @@
import { Field, Message } from 'protobufjs'
import { TransferAmount } from './TransferAmount'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { AbstractTransaction } from '../AbstractTransaction'
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
import { Account } from '@entity/Account'
import { Transaction } from '@entity/Transaction'
import Decimal from 'decimal.js-light'
import { Account } from '@entity/Account'
import { Field, Message } from 'protobufjs'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { AbstractTransaction } from '../AbstractTransaction'
import { TransferAmount } from './TransferAmount'
// https://www.npmjs.com/package/@apollo/protobufjs
// eslint-disable-next-line no-use-before-define
@ -31,17 +32,17 @@ export class GradidoTransfer extends Message<GradidoTransfer> implements Abstrac
}
}
// sender: TransferAmount contain
// - sender public key
// - amount
// - communityId // only set if not the same as sender and recipient community
@Field.d(1, TransferAmount)
public sender: TransferAmount
// the recipient public key
@Field.d(2, 'bytes')
public recipient: Buffer
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public validate(level: TransactionValidationLevel): boolean {
throw new Error('Method not implemented.')
}
public fillTransactionRecipe(recipe: Transaction): void {
recipe.amount = new Decimal(this.sender?.amount ?? 0)
}

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { AbstractTransaction } from '../AbstractTransaction'
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
import { Field, Message } from 'protobufjs'
import { Transaction } from '@entity/Transaction'
import { Field, Message } from 'protobufjs'
import { AbstractTransaction } from '../AbstractTransaction'
// connect group together
// only CrossGroupType CROSS (in TransactionBody)
@ -17,10 +17,6 @@ export class GroupFriendsUpdate extends Message<GroupFriendsUpdate> implements A
@Field.d(1, 'bool')
public colorFusion: boolean
public validate(level: TransactionValidationLevel): boolean {
throw new Error('Method not implemented.')
}
public fillTransactionRecipe(recipe: Transaction): void {
throw new Error('Method not implemented.')
}

View File

@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Transaction } from '@entity/Transaction'
import { Field, Message } from 'protobufjs'
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
import { AbstractTransaction } from '../AbstractTransaction'
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
import { Transaction } from '@entity/Transaction'
// https://www.npmjs.com/package/@apollo/protobufjs
// eslint-disable-next-line no-use-before-define
@ -25,9 +25,5 @@ export class RegisterAddress extends Message<RegisterAddress> implements Abstrac
@Field.d(5, 'uint32')
public derivationIndex?: number
public validate(level: TransactionValidationLevel): boolean {
throw new Error('Method not implemented.')
}
public fillTransactionRecipe(_recipe: Transaction): void {}
}

View File

@ -1,23 +1,24 @@
import { Transaction } from '@entity/Transaction'
import { Field, Message, OneOf } from 'protobufjs'
import { CrossGroupType } from './enum/CrossGroupType'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { LogError } from '@/server/LogError'
import { timestampToDate } from '@/utils/typeConverter'
import { Timestamp } from './Timestamp'
import { GradidoTransfer } from './GradidoTransfer'
import { AbstractTransaction } from '../AbstractTransaction'
import { determineCrossGroupType, determineOtherGroup } from '../transactionBody.logic'
import { CommunityRoot } from './CommunityRoot'
import { PROTO_TRANSACTION_BODY_VERSION_NUMBER } from './const'
import { CrossGroupType } from './enum/CrossGroupType'
import { TransactionType } from './enum/TransactionType'
import { GradidoCreation } from './GradidoCreation'
import { GradidoDeferredTransfer } from './GradidoDeferredTransfer'
import { GradidoTransfer } from './GradidoTransfer'
import { GroupFriendsUpdate } from './GroupFriendsUpdate'
import { RegisterAddress } from './RegisterAddress'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { determineCrossGroupType, determineOtherGroup } from '../transactionBody.logic'
import { CommunityRoot } from './CommunityRoot'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionType } from '@/graphql/enum/TransactionType'
import { AbstractTransaction } from '../AbstractTransaction'
import { Transaction } from '@entity/Transaction'
import { timestampToDate } from '@/utils/typeConverter'
import { LogError } from '@/server/LogError'
import { PROTO_TRANSACTION_BODY_VERSION_NUMBER } from './const'
import { Timestamp } from './Timestamp'
// https://www.npmjs.com/package/@apollo/protobufjs
// eslint-disable-next-line no-use-before-define
@ -95,6 +96,14 @@ export class TransactionBody extends Message<TransactionBody> {
else if (this.communityRoot) return TransactionType.COMMUNITY_ROOT
}
// The `TransactionBody` class utilizes Protobuf's `OneOf` field structure which, according to Protobuf documentation
// (https://protobuf.dev/programming-guides/proto3/#oneof), allows only one field within the group to be set at a time.
// Therefore, accessing the `getTransactionDetails()` method returns the first initialized value among the defined fields,
// each of which should be of type AbstractTransaction. It's important to note that due to the nature of Protobuf's `OneOf`,
// only one type from the defined options can be set within the object obtained from Protobuf.
//
// If multiple fields are set in a single object, the method `getTransactionDetails()` will return the first defined value
// based on the order of checks. Developers should handle this behavior according to the expected Protobuf structure.
public getTransactionDetails(): AbstractTransaction | undefined {
if (this.transfer) return this.transfer
if (this.creation) return this.creation

View File

@ -1,3 +1,8 @@
/**
* Enum for protobuf
* used from RegisterAddress to determine account type
* master implementation: https://github.com/gradido/gradido_protocol/blob/master/proto/gradido/register_address.proto
*/
export enum AddressType {
NONE = 0, // if no address was found
COMMUNITY_HUMAN = 1, // creation account for human
@ -7,13 +12,3 @@ export enum AddressType {
SUBACCOUNT = 5, // no creations allowed
CRYPTO_ACCOUNT = 6, // user control his keys, no creations
}
export function getAddressTypeEnumValue(typeString: string): AddressType | undefined {
// Iterate through all enum values
for (const key in AddressType) {
if (AddressType[key] === typeString) {
return AddressType[key] as unknown as AddressType
}
}
return undefined // If the string is not found
}

View File

@ -1,7 +1,22 @@
/**
* Enum for protobuf
* Determine Cross Group type of Transactions
* LOCAL: no cross group transactions, sender and recipient community are the same, only one transaction
* INBOUND: cross group transaction, Inbound part. On recipient community chain. Recipient side by Transfer Transactions
* OUTBOUND: cross group transaction, Outbound part. On sender community chain. Sender side by Transfer Transactions
* CROSS: for cross group transaction which haven't a direction like group friend update
* master implementation: https://github.com/gradido/gradido_protocol/blob/master/proto/gradido/transaction_body.proto
*
* Transaction Handling differ from database focused backend
* In Backend for each transfer transaction there are always two entries in db,
* on for sender user and one for recipient user despite storing basically the same data two times
* In Blockchain Implementation there only two transactions on cross group transactions, one for
* the sender community chain, one for the recipient community chain
* if the transaction stay in the community there is only one transaction
*/
export enum CrossGroupType {
LOCAL = 0,
INBOUND = 1,
OUTBOUND = 2,
// for cross group transaction which haven't a direction like group friend update
// CROSS = 3,
CROSS = 3,
}

View File

@ -0,0 +1,13 @@
/**
* based on TransactionBody data oneOf
* https://github.com/gradido/gradido_protocol/blob/master/proto/gradido/transaction_body.proto
* for storing type in db as number
*/
export enum TransactionType {
GRADIDO_TRANSFER = 1,
GRADIDO_CREATION = 2,
GROUP_FRIENDS_UPDATE = 3,
REGISTER_ADDRESS = 4,
GRADIDO_DEFERRED_TRANSFER = 5,
COMMUNITY_ROOT = 6,
}

View File

@ -1,9 +1,5 @@
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
import { Transaction } from '@entity/Transaction'
export abstract class AbstractTransaction {
// validate if transaction is valid, maybe expensive because depending on level several transactions will be fetched from db
public abstract validate(level: TransactionValidationLevel): boolean
public abstract fillTransactionRecipe(recipe: Transaction): void
}

View File

@ -1,13 +1,15 @@
import { TransactionBody } from './3_3/TransactionBody'
import { Account } from '@entity/Account'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { Community } from '@entity/Community'
import { InputTransactionType } from '@/graphql/enum/InputTransactionType'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { LogError } from '@/server/LogError'
import { CommunityRoot } from './3_3/CommunityRoot'
import { GradidoCreation } from './3_3/GradidoCreation'
import { GradidoTransfer } from './3_3/GradidoTransfer'
import { Community } from '@entity/Community'
import { CommunityRoot } from './3_3/CommunityRoot'
import { LogError } from '@/server/LogError'
import { TransactionBody } from './3_3/TransactionBody'
export class TransactionBodyBuilder {
private signingAccount?: Account

View File

@ -1,8 +1,9 @@
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { CrossGroupType } from './3_3/enum/CrossGroupType'
import { InputTransactionType } from '@/graphql/enum/InputTransactionType'
import { TransactionError } from '@/graphql/model/TransactionError'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { CrossGroupType } from './3_3/enum/CrossGroupType'
export const determineCrossGroupType = ({
senderUser,
@ -50,5 +51,7 @@ export const determineOtherGroup = (
)
}
return senderUser.communityUuid
case CrossGroupType.CROSS:
throw new TransactionError(TransactionErrorType.NOT_IMPLEMENTED_YET, 'not implemented yet')
}
}

View File

@ -1,7 +1,12 @@
import { registerEnumType } from 'type-graphql'
/**
* enum for graphql
* describe input account type in UserAccountDraft
* should have the same entries like enum AddressType from proto/enum folder
*/
export enum AccountType {
NONE = 'none', // if no address was found
NONE = 'NONE', // if no address was found
COMMUNITY_HUMAN = 'COMMUNITY_HUMAN', // creation account for human
COMMUNITY_GMW = 'COMMUNITY_GMW', // community public budget account
COMMUNITY_AUF = 'COMMUNITY_AUF', // community compensation and environment founds account

View File

@ -1,6 +1,7 @@
import { LogError } from '@/server/LogError'
import { registerEnumType } from 'type-graphql'
// enum for graphql but with int because it is the same in backend
// for transaction type from backend
export enum InputTransactionType {
CREATION = 1,
SEND = 2,
@ -11,14 +12,3 @@ registerEnumType(InputTransactionType, {
name: 'InputTransactionType', // this one is mandatory
description: 'Type of the transaction', // this one is optional
})
// from ChatGPT
export function getTransactionTypeString(id: InputTransactionType): string {
const key = Object.keys(InputTransactionType).find(
(key) => InputTransactionType[key as keyof typeof InputTransactionType] === id,
)
if (key === undefined) {
throw new LogError('invalid transaction type id: ' + id.toString())
}
return key
}

View File

@ -1,5 +1,7 @@
import { registerEnumType } from 'type-graphql'
// enum for graphql
// error groups for resolver answers
export enum TransactionErrorType {
NOT_IMPLEMENTED_YET = 'Not Implemented yet',
MISSING_PARAMETER = 'Missing parameter',

View File

@ -1,15 +0,0 @@
import { registerEnumType } from 'type-graphql'
export enum TransactionType {
GRADIDO_TRANSFER = 1,
GRADIDO_CREATION = 2,
GROUP_FRIENDS_UPDATE = 3,
REGISTER_ADDRESS = 4,
GRADIDO_DEFERRED_TRANSFER = 5,
COMMUNITY_ROOT = 6,
}
registerEnumType(TransactionType, {
name: 'TransactionType', // this one is mandatory
description: 'Type of the transaction', // this one is optional
})

View File

@ -1,15 +0,0 @@
import { registerEnumType } from 'type-graphql'
export enum TransactionValidationLevel {
SINGLE = 1, // check only the transaction
SINGLE_PREVIOUS = 2, // check also with previous transaction
DATE_RANGE = 3, // check all transaction from within date range by creation automatic the same month
PAIRED = 4, // check paired transaction on another group by cross group transactions
CONNECTED_GROUP = 5, // check all transactions in the group which connected with this transaction address(es)
CONNECTED_BLOCKCHAIN = 6, // check all transactions which connected with this transaction
}
registerEnumType(TransactionValidationLevel, {
name: 'TransactionValidationLevel',
description: 'Transaction Validation Levels',
})

View File

@ -2,6 +2,7 @@
import { IsBoolean, IsUUID } from 'class-validator'
import { Field, InputType } from 'type-graphql'
import { isValidDateString } from '@validator/DateString'
@InputType()

View File

@ -1,12 +1,13 @@
// https://www.npmjs.com/package/@apollo/protobufjs
import { IsEnum, IsObject, IsPositive, ValidateNested } from 'class-validator'
import { Decimal } from 'decimal.js-light'
import { InputTransactionType } from '@enum/InputTransactionType'
import { InputType, Field, Int } from 'type-graphql'
import { UserIdentifier } from './UserIdentifier'
import { InputTransactionType } from '@enum/InputTransactionType'
import { isValidDateString } from '@validator/DateString'
import { IsPositiveDecimal } from '@validator/Decimal'
import { IsEnum, IsObject, IsPositive, ValidateNested } from 'class-validator'
import { UserIdentifier } from './UserIdentifier'
@InputType()
export class TransactionDraft {

View File

@ -1,11 +1,14 @@
// https://www.npmjs.com/package/@apollo/protobufjs
import { InputType, Field } from 'type-graphql'
import { UserIdentifier } from './UserIdentifier'
import { isValidDateString } from '@validator/DateString'
import { IsEnum, IsObject, ValidateNested } from 'class-validator'
import { InputType, Field } from 'type-graphql'
import { isValidDateString } from '@validator/DateString'
import { AccountType } from '@/graphql/enum/AccountType'
import { UserIdentifier } from './UserIdentifier'
@InputType()
export class UserAccountDraft {
@Field(() => UserIdentifier)

View File

@ -1,5 +1,5 @@
import { ObjectType, Field, Int } from 'type-graphql'
import { Community as CommunityEntity } from '@entity/Community'
import { ObjectType, Field, Int } from 'type-graphql'
@ObjectType()
export class Community {

View File

@ -1,4 +1,5 @@
import { ObjectType, Field } from 'type-graphql'
import { TransactionErrorType } from '../enum/TransactionErrorType'
@ObjectType()

View File

@ -1,13 +1,20 @@
import { Field, Int, ObjectType } from 'type-graphql'
import { TransactionType } from '@enum/TransactionType'
import { Transaction } from '@entity/Transaction'
import { Field, Int, ObjectType } from 'type-graphql'
import { TransactionType } from '@/data/proto/3_3/enum/TransactionType'
import { LogError } from '@/server/LogError'
import { getEnumValue } from '@/utils/typeConverter'
@ObjectType()
export class TransactionRecipe {
public constructor({ id, createdAt, type, community }: Transaction) {
const transactionType = getEnumValue(TransactionType, type)
if (!transactionType) {
throw new LogError('invalid transaction, type is missing')
}
this.id = id
this.createdAt = createdAt.toString()
this.type = type
this.type = transactionType.toString()
this.topic = community.iotaTopic
}
@ -17,8 +24,8 @@ export class TransactionRecipe {
@Field(() => String)
createdAt: string
@Field(() => TransactionType)
type: TransactionType
@Field(() => String)
type: string
@Field(() => String)
topic: string

View File

@ -1,4 +1,5 @@
import { ObjectType, Field } from 'type-graphql'
import { TransactionError } from './TransactionError'
import { TransactionRecipe } from './TransactionRecipe'

View File

@ -1,10 +1,14 @@
import 'reflect-metadata'
import { ApolloServer } from '@apollo/server'
// must be imported before createApolloTestServer so that TestDB was created before createApolloTestServer imports repositories
import { TestDB } from '@test/TestDB'
import { createApolloTestServer } from '@test/ApolloServerMock'
import assert from 'assert'
import { ApolloServer } from '@apollo/server'
// must be imported before createApolloTestServer so that TestDB was created before createApolloTestServer imports repositories
// eslint-disable-next-line import/order
import { TestDB } from '@test/TestDB'
import { TransactionResult } from '@model/TransactionResult'
import { createApolloTestServer } from '@test/ApolloServerMock'
import { CONFIG } from '@/config'
CONFIG.IOTA_HOME_COMMUNITY_SEED = 'aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899'

View File

@ -1,17 +1,17 @@
import { Resolver, Query, Arg, Mutation, Args } from 'type-graphql'
import { CommunityDraft } from '@input/CommunityDraft'
import { TransactionResult } from '@model/TransactionResult'
import { TransactionError } from '@model/TransactionError'
import { TransactionErrorType } from '@enum/TransactionErrorType'
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
import { Community } from '@model/Community'
import { CommunityArg } from '@arg/CommunityArg'
import { LogError } from '@/server/LogError'
import { logger } from '@/server/logger'
import { TransactionErrorType } from '@enum/TransactionErrorType'
import { CommunityDraft } from '@input/CommunityDraft'
import { Community } from '@model/Community'
import { TransactionError } from '@model/TransactionError'
import { TransactionResult } from '@model/TransactionResult'
import { CommunityRepository } from '@/data/Community.repository'
import { AddCommunityContext } from '@/interactions/backendToDb/community/AddCommunity.context'
import { LogError } from '@/server/LogError'
import { logger } from '@/server/logger'
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
@Resolver()
export class CommunityResolver {

View File

@ -1,22 +1,28 @@
import 'reflect-metadata'
import { ApolloServer } from '@apollo/server'
// must be imported before createApolloTestServer so that TestDB was created before createApolloTestServer imports repositories
import { TestDB } from '@test/TestDB'
import { createApolloTestServer } from '@test/ApolloServerMock'
import assert from 'assert'
import { TransactionResult } from '@model/TransactionResult'
import { AccountFactory } from '@/data/Account.factory'
import { CONFIG } from '@/config'
import { UserFactory } from '@/data/User.factory'
import { UserAccountDraft } from '../input/UserAccountDraft'
import { UserLogic } from '@/data/User.logic'
import { AccountType } from '@enum/AccountType'
import { UserIdentifier } from '../input/UserIdentifier'
import { CommunityDraft } from '../input/CommunityDraft'
import { AddCommunityContext } from '@/interactions/backendToDb/community/AddCommunity.context'
import { InputTransactionType, getTransactionTypeString } from '../enum/InputTransactionType'
CONFIG.IOTA_HOME_COMMUNITY_SEED = 'aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899'
import { ApolloServer } from '@apollo/server'
// must be imported before createApolloTestServer so that TestDB was created before createApolloTestServer imports repositories
// eslint-disable-next-line import/order
import { TestDB } from '@test/TestDB'
import { AccountType } from '@enum/AccountType'
import { TransactionResult } from '@model/TransactionResult'
import { createApolloTestServer } from '@test/ApolloServerMock'
import { CONFIG } from '@/config'
import { AccountFactory } from '@/data/Account.factory'
import { KeyPair } from '@/data/KeyPair'
import { Mnemonic } from '@/data/Mnemonic'
import { UserFactory } from '@/data/User.factory'
import { UserLogic } from '@/data/User.logic'
import { AddCommunityContext } from '@/interactions/backendToDb/community/AddCommunity.context'
import { getEnumValue } from '@/utils/typeConverter'
import { InputTransactionType } from '../enum/InputTransactionType'
import { CommunityDraft } from '../input/CommunityDraft'
import { UserAccountDraft } from '../input/UserAccountDraft'
import { UserIdentifier } from '../input/UserIdentifier'
let apolloTestServer: ApolloServer
@ -32,7 +38,9 @@ jest.mock('@typeorm/DataSource', () => ({
getDataSource: jest.fn(() => TestDB.instance.dbConnect),
}))
CONFIG.IOTA_HOME_COMMUNITY_SEED = 'aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899'
const communityUUID = '3d813cbb-37fb-42ba-91df-831e1593ac29'
const communityKeyPair = new KeyPair(new Mnemonic(CONFIG.IOTA_HOME_COMMUNITY_SEED))
const createUserStoreAccount = async (uuid: string): Promise<UserIdentifier> => {
const userAccountDraft = new UserAccountDraft()
@ -41,11 +49,11 @@ const createUserStoreAccount = async (uuid: string): Promise<UserIdentifier> =>
userAccountDraft.user = new UserIdentifier()
userAccountDraft.user.uuid = uuid
userAccountDraft.user.communityUuid = communityUUID
const user = UserFactory.create(userAccountDraft)
const user = UserFactory.create(userAccountDraft, communityKeyPair)
const userLogic = new UserLogic(user)
const account = AccountFactory.createAccountFromUserAccountDraft(
userAccountDraft,
userLogic.calculateKeyPair(),
userLogic.calculateKeyPair(communityKeyPair),
)
account.user = user
// user is set to cascade: ['insert'] will be saved together with account
@ -82,7 +90,7 @@ describe('Transaction Resolver Test', () => {
input: {
senderUser,
recipientUser,
type: getTransactionTypeString(InputTransactionType.SEND),
type: getEnumValue(InputTransactionType, InputTransactionType.SEND),
amount: '10',
createdAt: '2012-04-17T17:12:00Z',
backendTransactionId: 1,
@ -130,7 +138,7 @@ describe('Transaction Resolver Test', () => {
input: {
senderUser,
recipientUser,
type: getTransactionTypeString(InputTransactionType.SEND),
type: getEnumValue(InputTransactionType, InputTransactionType.SEND),
amount: 'no number',
createdAt: '2012-04-17T17:12:00Z',
backendTransactionId: 1,
@ -156,7 +164,7 @@ describe('Transaction Resolver Test', () => {
input: {
senderUser,
recipientUser,
type: getTransactionTypeString(InputTransactionType.SEND),
type: getEnumValue(InputTransactionType, InputTransactionType.SEND),
amount: '10',
createdAt: 'not valid',
backendTransactionId: 1,
@ -192,7 +200,7 @@ describe('Transaction Resolver Test', () => {
input: {
senderUser,
recipientUser,
type: getTransactionTypeString(InputTransactionType.CREATION),
type: getEnumValue(InputTransactionType, InputTransactionType.CREATION),
amount: '10',
createdAt: '2012-04-17T17:12:00Z',
backendTransactionId: 1,

View File

@ -1,11 +1,14 @@
import { Resolver, Arg, Mutation } from 'type-graphql'
import { TransactionDraft } from '@input/TransactionDraft'
import { TransactionResult } from '../model/TransactionResult'
import { TransactionError } from '../model/TransactionError'
import { CreateTransactionRecipeContext } from '@/interactions/backendToDb/transaction/CreateTransationRecipe.context'
import { TransactionRecipe } from '../model/TransactionRecipe'
import { TransactionRepository } from '@/data/Transaction.repository'
import { CreateTransactionRecipeContext } from '@/interactions/backendToDb/transaction/CreateTransationRecipe.context'
import { TransactionErrorType } from '../enum/TransactionErrorType'
import { TransactionError } from '../model/TransactionError'
import { TransactionRecipe } from '../model/TransactionRecipe'
import { TransactionResult } from '../model/TransactionResult'
@Resolver()
export class TransactionResolver {

View File

@ -2,9 +2,9 @@ import { Decimal } from 'decimal.js-light'
import { GraphQLSchema } from 'graphql'
import { buildSchema } from 'type-graphql'
import { DecimalScalar } from './scalar/Decimal'
import { TransactionResolver } from './resolver/TransactionsResolver'
import { CommunityResolver } from './resolver/CommunityResolver'
import { TransactionResolver } from './resolver/TransactionsResolver'
import { DecimalScalar } from './scalar/Decimal'
export const schema = async (): Promise<GraphQLSchema> => {
return buildSchema({

View File

@ -1,14 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { CONFIG } from '@/config'
import createServer from './server/createServer'
import { KeyManager } from './manager/KeyManager'
async function main() {
// eslint-disable-next-line no-console
console.log(`DLT_CONNECTOR_PORT=${CONFIG.DLT_CONNECTOR_PORT}`)
const { app } = await createServer()
await KeyManager.getInstance().init()
app.listen(CONFIG.DLT_CONNECTOR_PORT, () => {
// eslint-disable-next-line no-console
console.log(`Server is running at http://localhost:${CONFIG.DLT_CONNECTOR_PORT}`)

View File

@ -1,8 +1,9 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
import { CommunityRole } from './Community.role'
import { ForeignCommunityRole } from './ForeignCommunity.role'
import { HomeCommunityRole } from './HomeCommunity.role'
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
import { CommunityRole } from './Community.role'
/**
* @DCI-Context

View File

@ -1,8 +1,9 @@
import { Community } from '@entity/Community'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { logger } from '@/server/logger'
import { Community } from '@entity/Community'
export abstract class CommunityRole {
protected self: Community

View File

@ -1,27 +1,28 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { Community } from '@entity/Community'
import { CommunityRole } from './Community.role'
import { Transaction } from '@entity/Transaction'
import { KeyManager } from '@/manager/KeyManager'
import { CONFIG } from '@/config'
import { AccountFactory } from '@/data/Account.factory'
import { CreateTransactionRecipeContext } from '../transaction/CreateTransationRecipe.context'
import { logger } from '@/server/logger'
import { TransactionError } from '@/graphql/model/TransactionError'
import { KeyPair } from '@/data/KeyPair'
import { Mnemonic } from '@/data/Mnemonic'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { logger } from '@/server/logger'
import { getDataSource } from '@/typeorm/DataSource'
import { CreateTransactionRecipeContext } from '../transaction/CreateTransationRecipe.context'
import { CommunityRole } from './Community.role'
export class HomeCommunityRole extends CommunityRole {
private transactionRecipe: Transaction
public async create(communityDraft: CommunityDraft, topic: string): Promise<void> {
super.create(communityDraft, topic)
// generate key pair for signing transactions and deriving all keys for community
const keyPair = KeyManager.generateKeyPair()
this.self.rootPubkey = keyPair.publicKey
this.self.rootPrivkey = keyPair.privateKey
this.self.rootChaincode = keyPair.chainCode
// we should only have one home community per server
KeyManager.getInstance().setHomeCommunityKeyPair(keyPair)
const keyPair = new KeyPair(new Mnemonic(CONFIG.IOTA_HOME_COMMUNITY_SEED ?? undefined))
keyPair.fillInCommunityKeys(this.self)
// create auf account and gmw account
this.self.aufAccount = AccountFactory.createAufAccount(keyPair, this.self.createdAt)

View File

@ -1,9 +1,10 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionRecipeRole } from './TransactionRecipe.role'
import { Community } from '@entity/Community'
import { TransactionBodyBuilder } from '@/data/proto/TransactionBody.builder'
import { KeyPair } from '@/data/KeyPair'
import { sign } from '@/utils/cryptoHelper'
import { TransactionBodyBuilder } from '@/data/proto/TransactionBody.builder'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionRecipeRole } from './TransactionRecipe.role'
export class CommunityRootTransactionRole extends TransactionRecipeRole {
public createFromCommunityRoot(
@ -18,7 +19,7 @@ export class CommunityRootTransactionRole extends TransactionRecipeRole {
this.transactionBuilder.fromTransactionBody(transactionBody).setCommunity(community)
const transaction = this.transactionBuilder.getTransaction()
// sign
this.transactionBuilder.setSignature(sign(transaction.bodyBytes, new KeyPair(community)))
this.transactionBuilder.setSignature(new KeyPair(community).sign(transaction.bodyBytes))
return this
}
}

View File

@ -1,11 +1,13 @@
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { Community } from '@entity/Community'
import { TransactionRecipeRole } from './TransactionRecipe.role'
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { Transaction } from '@entity/Transaction'
import { TransactionError } from '@/graphql/model/TransactionError'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
import { TransactionRecipeRole } from './TransactionRecipe.role'
/**
* @DCI-Context

View File

@ -1,12 +1,12 @@
import { Transaction } from '@entity/Transaction'
import { KeyPair } from '@/data/KeyPair'
import { TransactionBodyBuilder } from '@/data/proto/TransactionBody.builder'
import { TransactionBuilder } from '@/data/Transaction.builder'
import { UserRepository } from '@/data/User.repository'
import { TransactionBodyBuilder } from '@/data/proto/TransactionBody.builder'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
import { TransactionError } from '@/graphql/model/TransactionError'
import { sign } from '@/utils/cryptoHelper'
import { Transaction } from '@entity/Transaction'
export class TransactionRecipeRole {
protected transactionBuilder: TransactionBuilder
@ -52,7 +52,7 @@ export class TransactionRecipeRole {
const transaction = this.transactionBuilder.getTransaction()
// sign
this.transactionBuilder.setSignature(
sign(transaction.bodyBytes, new KeyPair(this.transactionBuilder.getCommunity())),
new KeyPair(this.transactionBuilder.getCommunity()).sign(transaction.bodyBytes),
)
return this
}

View File

@ -1,22 +0,0 @@
/* eslint-disable camelcase */
import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39'
import { generateFromSeed, toPublic } from 'bip32-ed25519'
describe('controller/KeyManager', () => {
describe('test crypto lib', () => {
it('key length', () => {
const mnemonic = entropyToMnemonic(
'aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899',
)
expect(mnemonic).toEqual(
'primary taxi danger target useless ancient match hammer fever crisp timber crew produce toy jeans that abandon math mimic master filter design carbon carbon',
)
const seed = mnemonicToSeedSync(mnemonic)
// private key 64 Bytes + 32 Byte ChainCode
const extendPrivkey = generateFromSeed(seed)
expect(extendPrivkey).toHaveLength(96)
// public key 32 Bytes + 32 Bytes ChainCode
expect(toPublic(extendPrivkey)).toHaveLength(64)
})
})
})

View File

@ -1,118 +0,0 @@
// eslint-disable-next-line camelcase
import { randombytes_buf } from 'sodium-native'
import { CONFIG } from '../config'
import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39'
// https://www.npmjs.com/package/bip32-ed25519?activeTab=code
import { generateFromSeed, derivePrivate, sign as ed25519Sign } from 'bip32-ed25519'
import { logger } from '@/server/logger'
import { LogError } from '@/server/LogError'
import { KeyPair } from '@/data/KeyPair'
import { GradidoTransaction } from '@/data/proto/3_3/GradidoTransaction'
import { CommunityRepository } from '@/data/Community.repository'
import { SignaturePair } from '@/data/proto/3_3/SignaturePair'
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
// and ../federation/client/FederationClientFactory.ts
/**
* A Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*/
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class KeyManager {
// eslint-disable-next-line no-use-before-define
private static instance: KeyManager
private homeCommunityRootKeys: KeyPair | null = null
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
private constructor() {}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(): KeyManager {
if (!KeyManager.instance) {
KeyManager.instance = new KeyManager()
}
return KeyManager.instance
}
public async init(): Promise<boolean> {
try {
this.homeCommunityRootKeys = await CommunityRepository.loadHomeCommunityKeyPair()
return true
} catch (error) {
logger.error('error by init key manager', error)
return false
}
}
public static generateKeyPair(): KeyPair {
const mnemonic = KeyManager.generateMnemonic(CONFIG.IOTA_HOME_COMMUNITY_SEED ?? undefined)
// logger.info('passphrase for key pair: ' + mnemonic)
const seed = mnemonicToSeedSync(mnemonic)
return new KeyPair(generateFromSeed(seed))
}
public setHomeCommunityKeyPair(keyPair: KeyPair) {
this.homeCommunityRootKeys = keyPair
}
public sign(transaction: GradidoTransaction, keys?: KeyPair[]) {
let localKeys: KeyPair[] = []
if (!keys && this.homeCommunityRootKeys) {
localKeys.push(this.homeCommunityRootKeys)
} else if (keys) {
localKeys = keys
}
if (!localKeys.length) {
throw new LogError('no key pair for signing')
}
localKeys.forEach((keyPair: KeyPair) => {
const signature = ed25519Sign(transaction.bodyBytes, keyPair.getExtendPrivateKey())
const sigPair = new SignaturePair({ pubKey: keyPair.publicKey, signature })
logger.debug('sign transaction', {
signature: signature.toString('hex'),
publicKey: keyPair.publicKey.toString('hex'),
bodyBytes: transaction.bodyBytes.toString('hex'),
})
transaction.sigMap.sigPair.push(sigPair)
})
}
public getHomeCommunityPublicKey(): Buffer | undefined {
if (!this.homeCommunityRootKeys) return undefined
return this.homeCommunityRootKeys.publicKey
}
public derive(path: number[], parentKeys?: KeyPair): KeyPair {
const extendedPrivateKey = parentKeys
? parentKeys.getExtendPrivateKey()
: this.homeCommunityRootKeys?.getExtendPrivateKey()
if (!extendedPrivateKey) {
throw new LogError('missing parent or root key pair')
}
return new KeyPair(
path.reduce(
(extendPrivateKey: Buffer, node: number) => derivePrivate(extendPrivateKey, node),
extendedPrivateKey,
),
)
}
static generateMnemonic(seed?: Buffer | string): string {
if (seed) {
return entropyToMnemonic(seed)
}
const entropy = Buffer.alloc(256)
randombytes_buf(entropy)
return entropyToMnemonic(entropy)
}
}

View File

@ -2,16 +2,16 @@ import 'reflect-metadata'
import { ApolloServer } from '@apollo/server'
import { expressMiddleware } from '@apollo/server/express4'
import bodyParser from 'body-parser'
import cors from 'cors'
import express, { Express } from 'express'
// graphql
import { Logger } from 'log4js'
import { schema } from '@/graphql/schema'
import { Connection } from '@/typeorm/DataSource'
import { logger as dltLogger } from './logger'
import { Logger } from 'log4js'
import cors from 'cors'
import bodyParser from 'body-parser'
import { Connection } from '@/typeorm/DataSource'
type ServerDef = { apollo: ApolloServer; app: Express }

View File

@ -1,7 +1,9 @@
import { readFileSync } from 'fs'
import log4js from 'log4js'
import { CONFIG } from '@/config'
import { readFileSync } from 'fs'
const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8'))
log4js.configure(options)

View File

@ -2,11 +2,11 @@
// We cannot use our connection here, but must use the external typeorm installation
import { DataSource as DBDataSource, FileLogger } from '@dbTools/typeorm'
import { entities } from '@entity/index'
import { Migration } from '@entity/Migration'
import { CONFIG } from '@/config'
import { logger } from '@/server/logger'
import { Migration } from '@entity/Migration'
import { LogError } from '@/server/LogError'
import { logger } from '@/server/logger'
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class Connection {

View File

@ -1,23 +0,0 @@
import { KeyPair } from '@/data/KeyPair'
import { sign as ed25519Sign, generateFromSeed } from 'bip32-ed25519'
import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39'
// eslint-disable-next-line camelcase
import { randombytes_buf } from 'sodium-native'
export const sign = (message: Buffer, keyPair: KeyPair): Buffer => {
return ed25519Sign(message, keyPair.getExtendPrivateKey())
}
export const generateKeyPair = (mnemonic: string): KeyPair => {
const seedFromMnemonic = mnemonicToSeedSync(mnemonic)
return new KeyPair(generateFromSeed(seedFromMnemonic))
}
export const generateMnemonic = (seed?: Buffer | string): string => {
if (seed) {
return entropyToMnemonic(seed)
}
const entropy = Buffer.alloc(256)
randombytes_buf(entropy)
return entropyToMnemonic(entropy)
}

View File

@ -1,5 +1,6 @@
import 'reflect-metadata'
import { Timestamp } from '../data/proto/3_3/Timestamp'
import { hardenDerivationIndex, HARDENED_KEY_BITMASK } from './derivationHelper'
import { timestampToDate } from './typeConverter'

View File

@ -1,5 +1,6 @@
import 'reflect-metadata'
import { Timestamp } from '@/data/proto/3_3/Timestamp'
import { timestampToDate } from './typeConverter'
describe('utils/typeConverter', () => {

View File

@ -1,14 +1,14 @@
import { crypto_generichash as cryptoHash } from 'sodium-native'
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
import { Timestamp } from '@/data/proto/3_3/Timestamp'
import { TimestampSeconds } from '@/data/proto/3_3/TimestampSeconds'
import { TransactionBody } from '@/data/proto/3_3/TransactionBody'
import { logger } from '@/server/logger'
import { TransactionError } from '@/graphql/model/TransactionError'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { AccountType } from '@/graphql/enum/AccountType'
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
import { TransactionError } from '@/graphql/model/TransactionError'
import { LogError } from '@/server/LogError'
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
import { logger } from '@/server/logger'
export const uuid4ToBuffer = (uuid: string): Buffer => {
// Remove dashes from the UUIDv4 string
@ -64,44 +64,44 @@ export const transactionBodyToBodyBytes = (transactionBody: TransactionBody): Bu
}
}
export const accountTypeToAddressType = (accountType: AccountType): AddressType => {
switch (accountType) {
case AccountType.NONE:
return AddressType.NONE
case AccountType.COMMUNITY_HUMAN:
return AddressType.COMMUNITY_HUMAN
case AccountType.COMMUNITY_GMW:
return AddressType.COMMUNITY_GMW
case AccountType.COMMUNITY_AUF:
return AddressType.COMMUNITY_AUF
case AccountType.COMMUNITY_PROJECT:
return AddressType.COMMUNITY_PROJECT
case AccountType.SUBACCOUNT:
return AddressType.SUBACCOUNT
case AccountType.CRYPTO_ACCOUNT:
return AddressType.CRYPTO_ACCOUNT
default:
throw new LogError(`Unsupported AccountType: ${accountType}`)
export function getEnumValue<T extends Record<string, unknown>>(
enumType: T,
value: number | string,
): T[keyof T] | undefined {
if (typeof value === 'number' && typeof enumType === 'object') {
return enumType[value as keyof T] as T[keyof T]
} else if (typeof value === 'string') {
for (const key in enumType) {
if (enumType[key as keyof T] === value) {
return enumType[key as keyof T] as T[keyof T]
}
}
}
return undefined
}
export const addressTypeToAccountType = (addressType: AddressType): AccountType => {
switch (addressType) {
case AddressType.NONE:
return AccountType.NONE
case AddressType.COMMUNITY_HUMAN:
return AccountType.COMMUNITY_HUMAN
case AddressType.COMMUNITY_GMW:
return AccountType.COMMUNITY_GMW
case AddressType.COMMUNITY_AUF:
return AccountType.COMMUNITY_AUF
case AddressType.COMMUNITY_PROJECT:
return AccountType.COMMUNITY_PROJECT
case AddressType.SUBACCOUNT:
return AccountType.SUBACCOUNT
case AddressType.CRYPTO_ACCOUNT:
return AccountType.CRYPTO_ACCOUNT
default:
throw new LogError(`Unsupported AddressType: ${addressType}`)
export const accountTypeToAddressType = (type: AccountType): AddressType => {
const typeString: string = AccountType[type]
const addressType: AddressType = AddressType[typeString as keyof typeof AddressType]
if (!addressType) {
throw new LogError("couldn't find corresponding AddressType for AccountType", {
accountType: type,
addressTypes: Object.keys(AddressType),
})
}
return addressType
}
export const addressTypeToAccountType = (type: AddressType): AccountType => {
const typeString: string = AddressType[type]
const accountType: AccountType = AccountType[typeString as keyof typeof AccountType]
if (!accountType) {
throw new LogError("couldn't find corresponding AccountType for AddressType", {
addressTypes: type,
accountType: Object.keys(AccountType),
})
}
return accountType
}

View File

@ -1,5 +1,6 @@
import { ApolloServer } from '@apollo/server'
import { addMocksToSchema } from '@graphql-tools/mock'
import { schema } from '@/graphql/schema'
let apolloTestServer: ApolloServer

View File

@ -1,7 +1,6 @@
import { DataSource, FileLogger } from '@dbTools/typeorm'
import { createDatabase } from 'typeorm-extension'
import { entities } from '@entity/index'
import { createDatabase } from 'typeorm-extension'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'