diff --git a/apps/cic-meta/.gitignore b/apps/cic-meta/.gitignore index 1a6cc5bb..34ca73fc 100644 --- a/apps/cic-meta/.gitignore +++ b/apps/cic-meta/.gitignore @@ -3,4 +3,3 @@ dist dist-web dist-server scratch -tests diff --git a/apps/cic-meta/package-lock.json b/apps/cic-meta/package-lock.json index c0c67d0d..8dd02add 100644 --- a/apps/cic-meta/package-lock.json +++ b/apps/cic-meta/package-lock.json @@ -1,6 +1,6 @@ { "name": "cic-client-meta", - "version": "0.0.7-alpha.2", + "version": "0.0.7-alpha.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -852,6 +852,75 @@ "printj": "~1.1.0" } }, + "crdt-meta": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/crdt-meta/-/crdt-meta-0.0.6.tgz", + "integrity": "sha512-uFn7OGCaUU7Wqa1d9wUZy4tE3zniq51wRFXU9fqV6fV2mTUjxXwow4aLBtONEEqVQRK0ivyqguQKp0WtiUMPAg==", + "requires": { + "automerge": "^0.14.2", + "ini": "^1.3.8", + "openpgp": "^4.10.8", + "pg": "^8.5.1", + "sqlite3": "^5.0.2" + }, + "dependencies": { + "automerge": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/automerge/-/automerge-0.14.2.tgz", + "integrity": "sha512-shiwuJHCbNRI23WZyIECLV4Ovf3WiAFJ7P9BH4l5gON1In/UUbjcSJKRygtIirObw2UQumeYxp3F2XBdSvQHnA==", + "requires": { + "immutable": "^3.8.2", + "transit-immutable-js": "^0.7.0", + "transit-js": "^0.8.861", + "uuid": "^3.4.0" + } + }, + "node-addon-api": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz", + "integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==" + }, + "pg": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz", + "integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.3.0", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "pg-pool": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz", + "integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg==" + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "sqlite3": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.2.tgz", + "integrity": "sha512-1SdTNo+BVU211Xj1csWa8lV6KM0CtucDwRyA0VHl91wEH1Mgh7RxUpI4rVvG7OhHrzCSGaVyW5g8vKvlrk9DJA==", + "requires": { + "node-addon-api": "^3.0.0", + "node-gyp": "3.x", + "node-pre-gyp": "^0.11.0" + } + } + } + }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -966,17 +1035,17 @@ "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" } }, "emoji-regex": { @@ -1489,9 +1558,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "interpret": { "version": "2.2.0", @@ -1957,9 +2026,9 @@ } }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, "yargs": { diff --git a/apps/cic-meta/package.json b/apps/cic-meta/package.json index ef706733..03d62dac 100644 --- a/apps/cic-meta/package.json +++ b/apps/cic-meta/package.json @@ -1,6 +1,6 @@ { "name": "cic-client-meta", - "version": "0.0.7-alpha.6", + "version": "0.0.7-alpha.7", "description": "Signed CRDT metadata graphs for the CIC network", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -15,8 +15,9 @@ "dependencies": { "@ethereumjs/tx": "^3.0.0-beta.1", "automerge": "^0.14.1", + "crdt-meta": "0.0.6", "ethereumjs-wallet": "^1.0.1", - "ini": "^1.3.5", + "ini": "^1.3.8", "openpgp": "^4.10.8", "pg": "^8.4.2", "sqlite3": "^5.0.0", diff --git a/apps/cic-meta/scripts/dumpconfig.js b/apps/cic-meta/scripts/dumpconfig.js index 526b6068..eae33056 100644 --- a/apps/cic-meta/scripts/dumpconfig.js +++ b/apps/cic-meta/scripts/dumpconfig.js @@ -1,4 +1,4 @@ -const config = require('./src/config'); +import { Config } from 'crdt-meta'; const fs = require('fs'); if (process.argv[2] === undefined) { @@ -15,6 +15,6 @@ try { process.exit(1); } -const c = new config.Config(process.argv[2], process.env['CONFINI_ENV_PREFIX']); +const c = new Config(process.argv[2], process.env['CONFINI_ENV_PREFIX']); c.process(); process.stdout.write(c.toString()); diff --git a/apps/cic-meta/scripts/server/handlers.ts b/apps/cic-meta/scripts/server/handlers.ts index c5245bf1..c433be2f 100644 --- a/apps/cic-meta/scripts/server/handlers.ts +++ b/apps/cic-meta/scripts/server/handlers.ts @@ -1,8 +1,7 @@ import * as Automerge from 'automerge'; import * as pgp from 'openpgp'; -import * as pg from 'pg'; -import { Envelope, Syncable } from '../../src/sync'; +import { Envelope, Syncable } from 'crdt-meta'; function handleNoMergeGet(db, digest, keystore) { diff --git a/apps/cic-meta/scripts/server/server.ts b/apps/cic-meta/scripts/server/server.ts index 9bef8bda..7c93ba11 100755 --- a/apps/cic-meta/scripts/server/server.ts +++ b/apps/cic-meta/scripts/server/server.ts @@ -1,15 +1,11 @@ import * as http from 'http'; import * as fs from 'fs'; import * as path from 'path'; -import * as pgp from 'openpgp'; import * as handlers from './handlers'; -import { Envelope, Syncable } from '../../src/sync'; -import { PGPKeyStore, PGPSigner } from '../../src/auth'; +import { PGPKeyStore, PGPSigner, Config, SqliteAdapter, PostgresAdapter } from 'crdt-meta'; import { standardArgs } from './args'; -import { Config } from '../../src/config'; -import { SqliteAdapter, PostgresAdapter } from '../../src/db'; let configPath = '/usr/local/etc/cic-meta'; diff --git a/apps/cic-meta/src/auth.ts b/apps/cic-meta/src/auth.ts deleted file mode 100644 index 182532c9..00000000 --- a/apps/cic-meta/src/auth.ts +++ /dev/null @@ -1,191 +0,0 @@ -import * as pgp from 'openpgp'; -import * as crypto from 'crypto'; - -interface Signable { - digest():string; -} - -type KeyGetter = () => any; - -type Signature = { - engine:string - algo:string - data:string - digest:string -} - -interface Signer { - prepare(Signable):boolean; - onsign(Signature):void; - onverify(boolean):void; - sign(digest:string):void - verify(digest:string, signature:Signature):void - fingerprint():string -} - -interface Authoritative { -} - -interface KeyStore { - getPrivateKey: KeyGetter - getFingerprint: () => string - getTrustedKeys: () => Array - getTrustedActiveKeys: () => Array - getEncryptKeys: () => Array -} - -class PGPKeyStore implements KeyStore { - - fingerprint: string - pk: any - - pubk = { - active: [], - trusted: [], - encrypt: [], - } - loads = 0x00; - loadsTarget = 0x0f; - onload: (k:KeyStore) => void; - - constructor(passphrase:string, pkArmor:string, pubkActiveArmor:string, pubkTrustedArmor:string, pubkEncryptArmor:string, onload = (ks:KeyStore) => {}) { - this._readKey(pkArmor, undefined, 1, passphrase); - this._readKey(pubkActiveArmor, 'active', 2); - this._readKey(pubkTrustedArmor, 'trusted', 4); - this._readKey(pubkEncryptArmor, 'encrypt', 8); - this.onload = onload; - } - - private _readKey(a:string, x:any, n:number, pass?:string) { - pgp.key.readArmored(a).then((k) => { - if (pass !== undefined) { - this.pk = k.keys[0]; - this.pk.decrypt(pass).then(() => { - this.fingerprint = this.pk.getFingerprint(); - console.log('private key (sign)', this.fingerprint); - this._registerLoad(n); - }); - } else { - this.pubk[x] = k.keys; - k.keys.forEach((pubk) => { - console.log('public key (' + x + ')', pubk.getFingerprint()); - }); - this._registerLoad(n); - } - }); - } - - private _registerLoad(b:number) { - this.loads |= b; - if (this.loads == this.loadsTarget) { - this.onload(this); - } - } - - public getTrustedKeys(): Array { - return this.pubk['trusted']; - } - - public getTrustedActiveKeys(): Array { - return this.pubk['active']; - - } - - public getEncryptKeys(): Array { - return this.pubk['encrypt']; - - } - - public getPrivateKey(): any { - return this.pk; - } - - public getFingerprint(): string { - return this.fingerprint; - } -} - -class PGPSigner implements Signer { - - engine = 'pgp' - algo = 'sha256' - dgst: string - signature: Signature - keyStore: KeyStore - onsign: (Signature) => void - onverify: (boolean) => void - - constructor(keyStore:KeyStore) { - this.keyStore = keyStore - this.onsign = (string) => {}; - this.onverify = (boolean) => {}; - } - - public fingerprint(): string { - return this.keyStore.getFingerprint(); - } - - public prepare(material:Signable):boolean { - this.dgst = material.digest(); - return true; - } - - public verify(digest:string, signature:Signature) { - pgp.signature.readArmored(signature.data).then((s) => { - const opts = { - message: pgp.cleartext.fromText(digest), - publicKeys: this.keyStore.getTrustedKeys(), - signature: s, - }; - pgp.verify(opts).then((v) => { - let i = 0; - for (i = 0; i < v.signatures.length; i++) { - const s = v.signatures[i]; - if (s.valid) { - this.onverify(s); - return; - } - } - console.error('checked ' + i + ' signature(s) but none valid'); - this.onverify(false); - }); - }).catch((e) => { - console.error(e); - this.onverify(false); - }); - } - - public sign(digest:string) { - const m = pgp.cleartext.fromText(digest); - const pk = this.keyStore.getPrivateKey(); - const opts = { - message: m, - privateKeys: [pk], - detached: true, - } - pgp.sign(opts).then((s) => { - this.signature = { - engine: this.engine, - algo: this.algo, - data: s.signature, - // TODO: fix for browser later - digest: digest, - }; - this.onsign(this.signature); - }).catch((e) => { - console.error(e); - this.onsign(undefined); - }); - } -} - -export { - Signature, - Authoritative, - Signer, - KeyGetter, - Signable, - KeyStore, - PGPSigner, - PGPKeyStore, -}; diff --git a/apps/cic-meta/src/config.ts b/apps/cic-meta/src/config.ts deleted file mode 100644 index e295f1cc..00000000 --- a/apps/cic-meta/src/config.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as fs from 'fs'; -import * as ini from 'ini'; -import * as path from 'path'; - -class Config { - - filepath: string - store: Object - censor: Array - require: Array - env_prefix: string - - constructor(filepath:string, env_prefix?:string) { - this.filepath = filepath; - this.store = {}; - this.censor = []; - this.require = []; - this.env_prefix = ''; - if (env_prefix !== undefined) { - this.env_prefix = env_prefix + "_"; - } - } - - public process() { - const d = fs.readdirSync(this.filepath); - - const r = /.*\.ini$/; - for (let i = 0; i < d.length; i++) { - const f = d[i]; - if (!f.match(r)) { - return; - } - - const fp = path.join(this.filepath, f); - const v = fs.readFileSync(fp, 'utf-8'); - const inid = ini.decode(v); - const inik = Object.keys(inid); - for (let j = 0; j < inik.length; j++) { - const k_section = inik[j] - const k = k_section.toUpperCase(); - Object.keys(inid[k_section]).forEach((k_directive) => { - const kk = k_directive.toUpperCase(); - const kkk = k + '_' + kk; - - let r = inid[k_section][k_directive]; - const k_env = this.env_prefix + kkk - const env = process.env[k_env]; - if (env !== undefined) { - console.debug('Environment variable ' + k_env + ' overrides ' + kkk); - r = env; - } - this.store[kkk] = r; - }); - } - } - } - - public get(s:string) { - return this.store[s]; - } - - public toString() { - let s = ''; - Object.keys(this.store).forEach((k) => { - s += k + '=' + this.store[k] + '\n'; - }); - return s; - } -} - -export { Config }; diff --git a/apps/cic-meta/src/constants.ts b/apps/cic-meta/src/constants.ts deleted file mode 100644 index 77587e6a..00000000 --- a/apps/cic-meta/src/constants.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { JSONSerializable } from './format'; - -const ENGINE_NAME = 'automerge'; -const ENGINE_VERSION = '0.14.1'; - -const NETWORK_NAME = 'cic'; -const NETWORK_VERSION = '1'; - -const CRYPTO_NAME = 'pgp'; -const CRYPTO_VERSION = '2'; - -type VersionedSpec = { - name: string - version: string - ext?: Object -} - -const engineSpec:VersionedSpec = { - name: ENGINE_NAME, - version: ENGINE_VERSION, -} - -const cryptoSpec:VersionedSpec = { - name: CRYPTO_NAME, - version: CRYPTO_VERSION, -} - -const networkSpec:VersionedSpec = { - name: NETWORK_NAME, - version: NETWORK_VERSION, -} - -export { - engineSpec, - cryptoSpec, - networkSpec, - VersionedSpec, -}; diff --git a/apps/cic-meta/src/crypto.ts b/apps/cic-meta/src/crypto.ts deleted file mode 100644 index 620527c0..00000000 --- a/apps/cic-meta/src/crypto.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as crypto from 'crypto'; - -const _algs = { - 'SHA-256': 'sha256', -} - -function cryptoWrapper() { -} - -cryptoWrapper.prototype.digest = async function(s, d) { - const h = crypto.createHash(_algs[s]); - h.update(d); - return h.digest(); -} - -let subtle = undefined; -if (typeof window !== 'undefined') { - subtle = window.crypto.subtle; -} else { - subtle = new cryptoWrapper(); -} - - -export { - subtle, -} - diff --git a/apps/cic-meta/src/db.ts b/apps/cic-meta/src/db.ts deleted file mode 100644 index 9d157cda..00000000 --- a/apps/cic-meta/src/db.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as pg from 'pg'; -import * as sqlite from 'sqlite3'; - -type DbConfig = { - name: string - host: string - port: number - user: string - password: string -} - -interface DbAdapter { - query: (s:string, callback:(e:any, rs:any) => void) => void - close: () => void -} - -const re_creatematch = /^(CREATE)/i -const re_getmatch = /^(SELECT)/i; -const re_setmatch = /^(INSERT|UPDATE)/i; - -class SqliteAdapter implements DbAdapter { - - db: any - - constructor(dbConfig:DbConfig, callback?:(any) => void) { - this.db = new sqlite.Database(dbConfig.name); //, callback); - } - - public query(s:string, callback:(e:any, rs?:any) => void): void { - const local_callback = (e, rs) => { - let r = undefined; - if (rs !== undefined) { - r = { - rowCount: rs.length, - rows: rs, - } - } - callback(e, r); - }; - if (s.match(re_getmatch)) { - this.db.all(s, local_callback); - } else if (s.match(re_setmatch)) { - this.db.run(s, local_callback); - } else if (s.match(re_creatematch)) { - this.db.run(s, callback); - } else { - throw 'unhandled query'; - } - } - - public close() { - this.db.close(); - } -} - -class PostgresAdapter implements DbAdapter { - - db: any - - constructor(dbConfig:DbConfig) { - let o = dbConfig; - o['database'] = o.name; - this.db = new pg.Pool(o); - return this.db; - } - - public query(s:string, callback:(e:any, rs:any) => void): void { - this.db.query(s, (e, rs) => { - let r = { - length: rs.rowCount, - } - rs.length = rs.rowCount; - if (e === undefined) { - e = null; - } - console.debug(e, rs); - callback(e, rs); - }); - } - - public close() { - this.db.end(); - } -} - -export { - DbConfig, - SqliteAdapter, - PostgresAdapter, -} diff --git a/apps/cic-meta/src/digest.ts b/apps/cic-meta/src/digest.ts deleted file mode 100644 index d66f7463..00000000 --- a/apps/cic-meta/src/digest.ts +++ /dev/null @@ -1,68 +0,0 @@ -import * as crypto from './crypto'; - -interface Addressable { - key(): string - digest(): string -} - -function stringToBytes(s:string) { - const a = new Uint8Array(20); - let j = 2; - for (let i = 0; i < a.byteLength; i++) { - const n = parseInt(s.substring(j, j+2), 16); - a[i] = n; - j += 2; - } - return a; -} - -function bytesToHex(a:Uint8Array) { - let s = ''; - for (let i = 0; i < a.byteLength; i++) { - const h = '00' + a[i].toString(16); - s += h.slice(-2); - } - return s; -} - -async function mergeKey(a:Uint8Array, s:Uint8Array) { - const y = new Uint8Array(a.byteLength + s.byteLength); - for (let i = 0; i < a.byteLength; i++) { - y[i] = a[i]; - } - for (let i = 0; i < s.byteLength; i++) { - y[a.byteLength + i] = s[i]; - } - const z = await crypto.subtle.digest('SHA-256', y); - return bytesToHex(new Uint8Array(z)); -} - -async function toKey(v:string, salt:string) { - const a = stringToBytes(v); - const s = new TextEncoder().encode(salt); - return await mergeKey(a, s); -} - - -async function toAddressKey(zeroExHex:string, salt:string) { - const a = addressToBytes(zeroExHex); - const s = new TextEncoder().encode(salt); - return await mergeKey(a, s); -} - -const re_addrHex = /^0[xX][a-fA-F0-9]{40}$/; -function addressToBytes(s:string) { - if (!s.match(re_addrHex)) { - throw 'invalid address hex'; - } - return stringToBytes(s); -} - -export { - toKey, - toAddressKey, - mergeKey, - bytesToHex, - addressToBytes, - Addressable, -} diff --git a/apps/cic-meta/src/dispatch.ts b/apps/cic-meta/src/dispatch.ts deleted file mode 100644 index b84ccb99..00000000 --- a/apps/cic-meta/src/dispatch.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { Syncable } from './sync'; -import { Store } from './store'; -import { PubSub } from './transport'; - -function toIndexKey(id:string):string { - const d = Date.now(); - return d + '_' + id + '_' + uuidv4(); -} - -const _re_indexKey = /^\d+_(.+)_[-\d\w]+$/; -function fromIndexKey(s:string):string { - const m = s.match(_re_indexKey); - if (m === null) { - throw 'Invalid index key'; - } - return m[1]; -} - -class Dispatcher { - - idx: Array - syncer: PubSub - store: Store - - constructor(store:Store, syncer:PubSub) { - this.idx = new Array() - this.syncer = syncer; - this.store = store; - } - - public isDirty(): boolean { - return this.idx.length > 0; - } - - public add(id:string, item:Syncable): string { - const v = item.toJSON(); - const k = toIndexKey(id); - this.store.put(k, v, true); - localStorage.setItem(k, v); - this.idx.push(k); - return k; - } - - public sync(offset:number): number { - let i = 0; - this.idx.forEach((k) => { - const v = localStorage.getItem(k); - const k_id = fromIndexKey(k); - this.syncer.pub(v); // this must block until guaranteed delivery - localStorage.removeItem(k); - i++; - }); - return i; - } -} - -export { Dispatcher, toIndexKey, fromIndexKey } diff --git a/apps/cic-meta/src/format.ts b/apps/cic-meta/src/format.ts deleted file mode 100644 index 3c223378..00000000 --- a/apps/cic-meta/src/format.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface JSONSerializable { - toJSON(): string -} - -export { JSONSerializable }; diff --git a/apps/cic-meta/src/index.ts b/apps/cic-meta/src/index.ts index ced4a31c..47006e15 100644 --- a/apps/cic-meta/src/index.ts +++ b/apps/cic-meta/src/index.ts @@ -1,5 +1,2 @@ -export { PGPSigner, PGPKeyStore, Signer, KeyStore } from './auth'; -export { ArgPair,  Envelope, Syncable } from './sync'; -export { User } from './assets/user'; -export { Phone } from './assets/phone'; -export { Config } from './config'; +export { User } from './user'; +export { Phone } from './phone'; diff --git a/apps/cic-meta/src/assets/phone.ts b/apps/cic-meta/src/phone.ts similarity index 82% rename from apps/cic-meta/src/assets/phone.ts rename to apps/cic-meta/src/phone.ts index 7ace058f..5bf893b6 100644 --- a/apps/cic-meta/src/assets/phone.ts +++ b/apps/cic-meta/src/phone.ts @@ -1,5 +1,4 @@ -import { ArgPair, Syncable } from '../sync'; -import { Addressable, mergeKey } from '../digest'; +import { Syncable, Addressable, mergeKey } from 'crdt-meta'; class Phone extends Syncable implements Addressable { diff --git a/apps/cic-meta/src/store.ts b/apps/cic-meta/src/store.ts deleted file mode 100644 index 26d6972c..00000000 --- a/apps/cic-meta/src/store.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Syncable } from './sync'; - -interface Store { - put(string, Syncable, boolean?) - get(string):Syncable - delete(string) -} - -export { Store }; diff --git a/apps/cic-meta/src/sync.ts b/apps/cic-meta/src/sync.ts deleted file mode 100644 index 21dcf87f..00000000 --- a/apps/cic-meta/src/sync.ts +++ /dev/null @@ -1,266 +0,0 @@ -import * as Automerge from 'automerge'; - -import { JSONSerializable } from './format'; - -import { Authoritative, Signer, PGPSigner, Signable, Signature } from './auth'; - -import { engineSpec, cryptoSpec, networkSpec, VersionedSpec } from './constants'; - -const fullSpec:VersionedSpec = { - name: 'cic', - version: '1', - ext: { - network: cryptoSpec, - engine: engineSpec, - }, -} - -class Envelope { - - o = fullSpec - - constructor(payload:Object) { - this.set(payload); - } - - public set(payload:Object) { - this.o['payload'] = payload - } - - public get():string { - return this.o['payload']; - } - - public toJSON() { - return JSON.stringify(this.o); - } - - public static fromJSON(s:string): Envelope { - const e = new Envelope(undefined); - e.o = JSON.parse(s); - return e; - } - - public unwrap(): Syncable { - return Syncable.fromJSON(this.o['payload']); - } -} - -class ArgPair { - - k:string - v:any - - constructor(k:string, v:any) { - this.k = k; - this.v = v; - } -} - -class SignablePart implements Signable { - - s: string - - constructor(s:string) { - this.s = s; - } - - public digest():string { - return this.s; - } -} - -function orderDict(src) { - let dst; - if (Array.isArray(src)) { - dst = []; - src.forEach((v) => { - if (typeof(v) == 'object') { - v = orderDict(v); - } - dst.push(v); - }); - } else { - dst = {} - Object.keys(src).sort().forEach((k) => { - let v = src[k]; - if (typeof(v) == 'object') { - v = orderDict(v); - } - dst[k] = v; - }); - } - return dst; -} - -class Syncable implements JSONSerializable, Authoritative, Signable { - - id: string - timestamp: number - m: any // automerge object - e: Envelope - signer: Signer - onwrap: (string) => void - onauthenticate: (boolean) => void - - // TODO: Move data to sub-object so timestamp, id, signature don't collide - constructor(id:string, v:Object) { - this.id = id; - const o = { - 'id': id, - 'timestamp': Math.floor(Date.now() / 1000), - 'data': v, - } - //this.m = Automerge.from(v) - this.m = Automerge.from(o) - } - - public setSigner(signer:Signer) { - this.signer = signer; - this.signer.onsign = (s) => { - this.wrap(s); - }; - } - - // TODO: To keep integrity, the non-link key/value pairs for each step also need to be hashed - public digest(): string { - const links = []; - Automerge.getAllChanges(this.m).forEach((ch:Object) => { - const op:Array = ch['ops']; - ch['ops'].forEach((op:Array) => { - if (op['action'] == 'link') { - //console.log('op link', op); - links.push([op['obj'], op['value']]); - } - }); - }); - //return JSON.stringify(links); - const j = JSON.stringify(links); - return Buffer.from(j).toString('base64'); - } - - private wrap(s:any) { - this.m = Automerge.change(this.m, 'sign', (doc) => { - doc['signature'] = s; - }); - this.e = new Envelope(this.toJSON()); - console.log('wrappin s', s, typeof(s)); - this.e.o['digest'] = s.digest; - if (this.onwrap !== undefined) { - this.onwrap(this.e); - } - - } - -// private _verifyLoop(i:number, history:Array, signable:Signable, result:boolean) { -// if (!result) { -// this.onauthenticate(false); -// return; -// } else if (history.length == 0) { -// this.onauthenticate(true); -// return; -// } -// const h = history.shift() -// if (i % 2 == 0) { -// i++; -// signable = { -// digest: () => { -// return Automerge.save(h.snapshot) -// }, -// }; -// this._verifyLoop(i, history, signable, true); -// } else { -// i++; -// const signature = h.snapshot['signature']; -// console.debug('signature', signature, signable.digest()); -// this.signer.onverify = (v) => { -// this._verifyLoop(i, history, signable, v) -// } -// this.signer.verify(signable, signature); -// } -// } -// -// // TODO: This should replay the graph and check signatures on each step -// public _authenticate(full:boolean=false) { -// let h = Automerge.getHistory(this.m); -// h.forEach((m) => { -// //console.debug(m.snapshot); -// }); -// const signable = { -// digest: () => { return '' }, -// } -// if (!full) { -// h = h.slice(h.length-2); -// } -// this._verifyLoop(0, h, signable, true); -// } - - public authenticate(full:boolean=false) { - if (full) { - console.warn('only doing shallow authentication for now, sorry'); - } - //console.log('authenticating', signable.digest()); - //console.log('signature', this.m.signature); - this.signer.onverify = (v) => { - //this._verifyLoop(i, history, signable, v) - this.onauthenticate(v); - } - this.signer.verify(this.m.signature.digest, this.m.signature); - } - - - public sign() { - //this.signer.prepare(this); - this.signer.sign(this.digest()); - } - - public update(changes:Array, changesDescription:string) { - this.m = Automerge.change(this.m, changesDescription, (m) => { - changes.forEach((c) => { - let path = c.k.split('.'); - let target = m['data']; - while (path.length > 1) { - const part = path.shift(); - target = target[part]; - } - target[path[0]] = c.v; - }); - m['timestamp'] = Math.floor(Date.now() / 1000); - }); - } - - public replace(o:Object, changesDescription:string) { - this.m = Automerge.change(this.m, changesDescription, (m) => { - Object.keys(o).forEach((k) => { - m['data'][k] = o[k]; - }); - Object.keys(m).forEach((k) => { - if (o[k] == undefined) { - delete m['data'][k]; - } - }); - m['timestamp'] = Math.floor(Date.now() / 1000); - }); - } - - public merge(s:Syncable) { - this.m = Automerge.merge(s.m, this.m); - } - - public toJSON(): string { - const s = Automerge.save(this.m); - const o = JSON.parse(s); - const oo = orderDict(o) - return JSON.stringify(oo); - - } - - public static fromJSON(s:string): Syncable { - const doc = Automerge.load(s); - let y = new Syncable(doc['id'], {}); - y.m = doc - return y - } -} - -export { JSONSerializable, Syncable, ArgPair, Envelope }; diff --git a/apps/cic-meta/src/transport.ts b/apps/cic-meta/src/transport.ts deleted file mode 100644 index fa3cda6f..00000000 --- a/apps/cic-meta/src/transport.ts +++ /dev/null @@ -1,11 +0,0 @@ -interface SubConsumer { - post(string) -} - -interface PubSub { - pub(v:string):boolean - close() -} - -export { PubSub, SubConsumer }; - diff --git a/apps/cic-meta/src/assets/user.ts b/apps/cic-meta/src/user.ts similarity index 87% rename from apps/cic-meta/src/assets/user.ts rename to apps/cic-meta/src/user.ts index 7ac99908..3faa5acf 100644 --- a/apps/cic-meta/src/assets/user.ts +++ b/apps/cic-meta/src/user.ts @@ -1,5 +1,4 @@ -import { ArgPair, Syncable } from '../sync'; -import { Addressable, addressToBytes, bytesToHex, toAddressKey } from '../digest'; +import { Syncable, Addressable, toAddressKey } from 'crdt-meta'; const keySalt = new TextEncoder().encode(':cic.person'); class User extends Syncable implements Addressable { diff --git a/apps/cic-meta/tests/1_basic.ts b/apps/cic-meta/tests/1_basic.ts deleted file mode 100644 index 3167a83f..00000000 --- a/apps/cic-meta/tests/1_basic.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as Automerge from 'automerge'; -import assert = require('assert'); - -import { Dispatcher, toIndexKey, fromIndexKey } from '../src/dispatch'; -import { User } from '../src/assets/user'; -import { Syncable, ArgPair } from '../src/sync'; - -import { MockSigner, MockStore } from './mock'; - -describe('basic', () => { - - it('store', () => { - const store = new MockStore('s'); - assert.equal(store.name, 's'); - - const mockSigner = new MockSigner(); - const v = new Syncable('foo', {baz: 42}); - v.setSigner(mockSigner); - store.put('foo', v); - const one = store.get('foo').toJSON(); - const vv = new Syncable('bar', {baz: 666}); - vv.setSigner(mockSigner); - assert.throws(() => { - store.put('foo', vv) - }); - store.put('foo', vv, true); - const other = store.get('foo').toJSON(); - assert.notEqual(one, other); - store.delete('foo'); - assert.equal(store.get('foo'), undefined); - }); - - it('add_doc_to_dispatcher', () => { - const store = new MockStore('s'); - //const syncer = new MockSyncer(); - const dispatcher = new Dispatcher(store, undefined); - const user = new User('foo'); - dispatcher.add(user.id, user); - assert(dispatcher.isDirty()); - }); - - it('dispatch_keyindex', () => { - const s = 'foo'; - const k = toIndexKey(s); - const v = fromIndexKey(k); - assert.equal(s, v); - }); - - -}); diff --git a/apps/cic-meta/tests/2_sync.ts b/apps/cic-meta/tests/2_sync.ts deleted file mode 100644 index aba024ea..00000000 --- a/apps/cic-meta/tests/2_sync.ts +++ /dev/null @@ -1,212 +0,0 @@ -import * as Automerge from 'automerge'; -import assert = require('assert'); - -import * as pgp from 'openpgp'; -import * as fs from 'fs'; - -import { PGPSigner } from '../src/auth'; - -import { Syncable, ArgPair } from '../src/sync'; - -import { MockKeyStore, MockSigner } from './mock'; - - -describe('sync', async () => { - it('sync_merge', () => { - const mockSigner = new MockSigner(); - const s = new Syncable('foo', { - bar: 'baz', - }); - s.setSigner(mockSigner); - const changePair = new ArgPair('xyzzy', 42); - s.update([changePair], 'ch-ch-cha-changes'); - assert.equal(s.m.data['xyzzy'], 42) - assert.equal(s.m.data['bar'], 'baz') - assert.equal(s.m['id'], 'foo') - assert.equal(Automerge.getHistory(s.m).length, 2); - }); - - it('sync_serialize', () => { - const mockSigner = new MockSigner(); - const s = new Syncable('foo', { - bar: 'baz', - }); - s.setSigner(mockSigner); - const j = s.toJSON(); - const ss = Syncable.fromJSON(j); - assert.equal(ss.m['id'], 'foo'); - assert.equal(ss.m['data']['bar'], 'baz'); - assert.equal(Automerge.getHistory(ss.m).length, 1); - }); - - it('sync_sign_and_wrap', () => { - const mockSigner = new MockSigner(); - const s = new Syncable('foo', { - bar: 'baz', - }); - s.setSigner(mockSigner); - s.onwrap = (e) => { - const j = e.toJSON(); - const v = JSON.parse(j); - assert.deepEqual(v.payload, e.o.payload); - - } - s.sign(); - }); - it('sync_verify_success', async () => { - const pksa = fs.readFileSync(__dirname + '/privatekeys.asc'); - const pks = await pgp.key.readArmored(pksa); - await pks.keys[0].decrypt('merman'); - await pks.keys[1].decrypt('beastman'); - - const pubksa = fs.readFileSync(__dirname + '/publickeys.asc'); - const pubks = await pgp.key.readArmored(pubksa); - - const oneStore = new MockKeyStore(pks.keys[0], pubks.keys); - const twoStore = new MockKeyStore(pks.keys[1], pubks.keys); - const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]); - - const oneSigner = new PGPSigner(oneStore); - const twoSigner = new PGPSigner(twoStore); - const threeSigner = new PGPSigner(threeStore); - - const x = new Syncable('foo', { - bar: 'baz', - }); - x.setSigner(oneSigner); - - // TODO: make this look better - x.onwrap = (e) => { - let updateData = new ArgPair('bar', 'xyzzy'); - x.update([updateData], 'change one'); - - x.onwrap = (e) => { - x.setSigner(twoSigner); - updateData = new ArgPair('bar', 42); - x.update([updateData], 'change two'); - - x.onwrap = (e) => { - const p = e.unwrap(); - p.setSigner(twoSigner); - p.onauthenticate = (v) => { - assert(v); - } - p.authenticate(); - } - - x.sign(); - }; - - x.sign(); - } - - x.sign(); - - }); - - it('sync_verify_fail', async () => { - const pksa = fs.readFileSync(__dirname + '/privatekeys.asc'); - const pks = await pgp.key.readArmored(pksa); - await pks.keys[0].decrypt('merman'); - await pks.keys[1].decrypt('beastman'); - - const pubksa = fs.readFileSync(__dirname + '/publickeys.asc'); - const pubks = await pgp.key.readArmored(pubksa); - - const oneStore = new MockKeyStore(pks.keys[0], pubks.keys); - const twoStore = new MockKeyStore(pks.keys[1], pubks.keys); - const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]); - - const oneSigner = new PGPSigner(oneStore); - const twoSigner = new PGPSigner(twoStore); - const threeSigner = new PGPSigner(threeStore); - - const x = new Syncable('foo', { - bar: 'baz', - }); - x.setSigner(oneSigner); - - // TODO: make this look better - x.onwrap = (e) => { - let updateData = new ArgPair('bar', 'xyzzy'); - x.update([updateData], 'change one'); - - x.onwrap = (e) => { - x.setSigner(twoSigner); - updateData = new ArgPair('bar', 42); - x.update([updateData], 'change two'); - - x.onwrap = (e) => { - const p = e.unwrap(); - p.setSigner(threeSigner); - p.onauthenticate = (v) => { - assert(!v); - } - p.authenticate(); - } - - x.sign(); - }; - - x.sign(); - } - - x.sign(); - - }); - - xit('sync_verify_shallow_tricked', async () => { - const pksa = fs.readFileSync(__dirname + '/privatekeys.asc'); - const pks = await pgp.key.readArmored(pksa); - await pks.keys[0].decrypt('merman'); - await pks.keys[1].decrypt('beastman'); - - const pubksa = fs.readFileSync(__dirname + '/publickeys.asc'); - const pubks = await pgp.key.readArmored(pubksa); - - const oneStore = new MockKeyStore(pks.keys[0], pubks.keys); - const twoStore = new MockKeyStore(pks.keys[1], pubks.keys); - const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]); - - const oneSigner = new PGPSigner(oneStore); - const twoSigner = new PGPSigner(twoStore); - const threeSigner = new PGPSigner(threeStore); - - const x = new Syncable('foo', { - bar: 'baz', - }); - x.setSigner(twoSigner); - - // TODO: make this look better - x.onwrap = (e) => { - let updateData = new ArgPair('bar', 'xyzzy'); - x.update([updateData], 'change one'); - - x.onwrap = (e) => { - updateData = new ArgPair('bar', 42); - x.update([updateData], 'change two'); - x.setSigner(oneSigner); - - x.onwrap = (e) => { - const p = e.unwrap(); - p.setSigner(threeSigner); - p.onauthenticate = (v) => { - assert(v); - p.onauthenticate = (v) => { - assert(!v); - } - p.authenticate(true); - } - p.authenticate(); - } - - x.sign(); - }; - - x.sign(); - } - - x.sign(); - - }); -}); diff --git a/apps/cic-meta/tests/3_transport.ts b/apps/cic-meta/tests/3_transport.ts deleted file mode 100644 index 6bae044c..00000000 --- a/apps/cic-meta/tests/3_transport.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as assert from 'assert'; - -import { MockPubSub, MockConsumer } from './mock'; - -describe('transport', () => { - it('pub_sub', () => { - const c = new MockConsumer(); - const ps = new MockPubSub('foo', c); - ps.pub('foo'); - ps.pub('bar'); - ps.flush(); - assert.deepEqual(c.omnoms, ['foo', 'bar']); - }); -}); diff --git a/apps/cic-meta/tests/4_auth.ts b/apps/cic-meta/tests/4_auth.ts deleted file mode 100644 index 33bba840..00000000 --- a/apps/cic-meta/tests/4_auth.ts +++ /dev/null @@ -1,46 +0,0 @@ -import assert = require('assert'); -import pgp = require('openpgp'); -import crypto = require('crypto'); - -import { Syncable, ArgPair } from '../src/sync'; - -import { MockKeyStore, MockSignable } from './mock'; - -import { PGPSigner } from '../src/auth'; - - -describe('auth', async () => { - await it('digest', async () => { - const opts = { - userIds: [ - { - name: 'John Marston', - email: 'red@dead.com', - }, - ], - numBits: 2048, - passphrase: 'foo', - }; - const pkgen = await pgp.generateKey(opts); - const pka = pkgen.privateKeyArmored; - const pks = await pgp.key.readArmored(pka); - await pks.keys[0].decrypt('foo'); - const pubka = pkgen.publicKeyArmored; - const pubks = await pgp.key.readArmored(pubka); - const keyStore = new MockKeyStore(pks.keys[0], pubks.keys); - const s = new PGPSigner(keyStore); - - const message = await pgp.cleartext.fromText('foo'); - s.onverify = (ok) => { - assert(ok); - } - s.onsign = (signature) => { - s.onverify((v) => { - console.log('bar', v); - }); - s.verify('foo', signature); - } - - await s.sign('foo'); - }); -}); diff --git a/apps/cic-meta/tests/999_functional.ts b/apps/cic-meta/tests/999_functional.ts deleted file mode 100644 index 9ed90777..00000000 --- a/apps/cic-meta/tests/999_functional.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as assert from 'assert'; -import * as pgp from 'openpgp'; - -import { Dispatcher } from '../src/dispatch'; -import { User } from '../src/assets/user'; -import { PGPSigner, KeyStore } from '../src/auth'; -import { SubConsumer } from '../src/transport'; - -import { MockStore, MockPubSub, MockConsumer, MockKeyStore } from './mock'; - -async function createKeyStore() { - const opts = { - userIds: [ - { - name: 'John Marston', - email: 'red@dead.com', - }, - ], - numBits: 2048, - passphrase: 'foo', - }; - const pkgen = await pgp.generateKey(opts); - const pka = pkgen.privateKeyArmored; - const pks = await pgp.key.readArmored(pka); - await pks.keys[0].decrypt('foo'); - return new MockKeyStore(pks.keys[0], []); -} - -describe('fullchain', async () => { - it('dispatch_and_publish_user', async () => { - const g = await createKeyStore(); - const n = new PGPSigner(g); - const u = new User('u1', {}); - u.setSigner(n); - u.setName('Nico', 'Bellic'); - const s = new MockStore('fooStore'); - const c = new MockConsumer(); - const p = new MockPubSub('fooPubSub', c); - const d = new Dispatcher(s, p); - u.onwrap = (e) => { - d.add(u.id, e); - d.sync(0); - assert.equal(p.pubs.length, 1); - }; - u.sign(); - }); -}); diff --git a/apps/cic-meta/tests/mock.ts b/apps/cic-meta/tests/mock.ts deleted file mode 100644 index c9ef81ad..00000000 --- a/apps/cic-meta/tests/mock.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as crypto from 'crypto'; - -import { Signable, Signature, KeyStore } from '../src/auth'; -import { Store } from '../src/store'; -import { PubSub, SubConsumer } from '../src/transport'; -import { Syncable } from '../src/sync'; - -class MockStore implements Store { - - contents: Object - name: string - - constructor(name:string) { - this.name = name; - this.contents = {}; - } - - public put(k:string, v:Syncable, existsOk = false) { - if (!existsOk && this.contents[k] !== undefined) { - throw '"' + k + '" already exists in store ' + this.name; - }  - this.contents[k] = v; - } - - public get(k:string): Syncable { - return this.contents[k]; - } - - public delete(k:string) { - delete this.contents[k]; - } -} - -class MockSigner { - onsign: (string) => void - onverify: (boolean) => void - public verify(src:string, signature:Signature) { - return true; - } - - public sign(s:string):boolean { - this.onsign('there would be a signature here'); - return true; - } - - public prepare(m:Signable):boolean { - return true; - } - - public fingerprint():string { - return ''; - } -} - -class MockConsumer implements SubConsumer { - - omnoms: Array - - constructor() { - this.omnoms = Array(); - } - - public post(v:string) { - this.omnoms.push(v); - } -} - -class MockPubSub implements PubSub { - - pubs: Array - consumer: SubConsumer - - constructor(name:string, consumer:SubConsumer) { - this.pubs = Array(); - this.consumer = consumer; - } - - public pub(v:string): boolean { - this.pubs.push(v); - return true; - } - - public flush() { - while (this.pubs.length > 0) { - const s = this.pubs.shift(); - this.consumer.post(s); - } - } - - public close() { - } -} - -class MockSignable implements Signable { - - src: string - dst: string - - constructor(src:string) { - this.src = src; - } - - public digest():string { - const h = crypto.createHash('sha256'); - h.update(this.src); - this.dst= h.digest('hex'); - return this.dst; - } - -} - -class MockKeyStore implements KeyStore { - - pk: any - pubks: Array - - constructor(pk:any, pubks:Array) { - this.pk = pk; - this.pubks = pubks; - } - - public getPrivateKey(): any { - return this.pk; - } - - public getTrustedKeys(): Array { - return this.pubks; - } - - public getTrustedActiveKeys(): Array { - return []; - } - - public getEncryptKeys(): Array { - return []; - } - - public getFingerprint(): string { - return ''; - } -} - -export { - MockStore, - MockPubSub, - MockConsumer, - MockSignable, - MockKeyStore, - MockSigner, -}; diff --git a/apps/cic-meta/tests/server.ts b/apps/cic-meta/tests/server.ts index 3071c096..d1399025 100644 --- a/apps/cic-meta/tests/server.ts +++ b/apps/cic-meta/tests/server.ts @@ -1,13 +1,10 @@ -import Automerge = require('automerge'); import assert = require('assert'); import fs = require('fs'); import pgp = require('openpgp'); import sqlite = require('sqlite3'); import * as handlers from '../scripts/server/handlers'; -import { Envelope, Syncable, ArgPair } from '../src/sync'; -import { PGPKeyStore, PGPSigner, KeyStore, Signer } from '../src/auth'; -import { SqliteAdapter } from '../src/db'; +import { Envelope, Syncable, ArgPair, PGPKeyStore, PGPSigner, KeyStore, Signer, SqliteAdapter } from 'crdt-meta'; function createKeystore() { const pksa = fs.readFileSync(__dirname + '/privatekeys.asc', 'utf-8');