Skip to content

Commit

Permalink
Merge pull request #168 from mediachain/yn-key-class-refactor
Browse files Browse the repository at this point in the history
Refactor signing keys, Publisher Id into classes
  • Loading branch information
yusefnapora committed Jan 19, 2017
2 parents 0bdba11 + 0299fe3 commit f0b0936
Show file tree
Hide file tree
Showing 30 changed files with 220 additions and 74 deletions.
4 changes: 2 additions & 2 deletions integration-test/push_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { promiseHash } = require('../src/common/util')
const { getTestNodeId } = require('../test/util')
const { MediachainNode: AlephNode } = require('../src/peer/node')
const { concatNodeClient, concatNodePeerInfo } = require('./util')
const { generatePublisherId } = require('../src/peer/identity')
const { PublisherId } = require('../src/peer/identity')
const { makeSimpleStatement } = require('../src/metadata/statement')

const TEST_NAMESPACE = 'scratch.push-test'
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('Push', () => {
let statementIds
let unauthorizedStatementId

before(() => generatePublisherId()
before(() => PublisherId.generate()
.then(_publisherId => { publisherId = _publisherId })
.then(() => getTestNodeId())
.then(nodeId => {
Expand Down
2 changes: 1 addition & 1 deletion npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions src/metadata/signatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

const { omit, cloneDeep } = require('lodash')
const pb = require('../protobuf')
const { publisherKeyFromB58String, signBuffer, verifyBuffer } = require('../peer/identity')
const { PublicSigningKey } = require('../peer/identity')

import type { PublisherId, PublicSigningKey } from '../peer/identity'
import type { PublisherId } from '../peer/identity'
import type { StatementMsg } from '../protobuf/types'

function signStatement (stmt: StatementMsg, publisherId: PublisherId): Promise<StatementMsg> {
Expand All @@ -20,13 +20,13 @@ function calculateSignature (stmt: StatementMsg, publisherId: PublisherId): Prom
return Promise.resolve().then(() => {
const bytes = pb.stmt.Statement.encode(stmt)
// sign the encoded statement message and set the signature
return signBuffer(publisherId.privateKey, bytes)
return publisherId.sign(bytes)
})
}

function verifyStatement (stmt: StatementMsg): Promise<boolean> {
return Promise.resolve()
.then(() => publisherKeyFromB58String(stmt.publisher))
.then(() => PublicSigningKey.fromB58String(stmt.publisher))
.then(pubKey => verifyStatementSignature(stmt, pubKey))
}

Expand All @@ -36,7 +36,7 @@ function verifyStatementSignature (stmt: StatementMsg, publicKey: PublicSigningK
const sig = stmt.signature
const withoutSig = omit(cloneDeep(stmt), 'signature')
const bytes = pb.stmt.Statement.encode(withoutSig)
return verifyBuffer(publicKey, bytes, sig)
return publicKey.verify(bytes, sig)
})
}

Expand All @@ -45,7 +45,7 @@ function verifyStatementWithKeyCache (stmt: StatementMsg, cache: Map<string, Pub
.then(() => {
const maybeKey = cache.get(stmt.publisher)
if (maybeKey != null) return maybeKey
const key = publisherKeyFromB58String(stmt.publisher)
const key = PublicSigningKey.fromB58String(stmt.publisher)
cache.set(stmt.publisher, key)
return key
}).then(pubKey => verifyStatementSignature(stmt, pubKey))
Expand Down
171 changes: 118 additions & 53 deletions src/peer/identity.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
const thenifyAll = require('thenify-all')
const path = require('path')
const fs = thenifyAll(require('fs'), {}, ['readFile'])
const fs = thenifyAll(require('fs'), {}, ['readFile', 'writeFile'])
const PeerId = thenifyAll(require('peer-id'), {}, ['createFromPrivKey', 'create'])
const PeerInfo = require('peer-info')
const Crypto = require('libp2p-crypto')
const Crypto = thenifyAll(require('libp2p-crypto'), {}, ['unmarshalPrivateKey', 'generateKeyPair'])
const Multiaddr = require('multiaddr')
const b58 = require('bs58')
const { b58MultihashForBuffer } = require('../common/util')

const NODE_KEY_TYPE = 'RSA'
const NODE_KEY_BITS = 2048
Expand Down Expand Up @@ -92,69 +91,137 @@ function inflateMultiaddr (multiaddrString: string): PeerInfo {
// eslint (and thus standard.js) doesn't like flow interfaces,
// so we need to temporarily disable the no-undef rule
/* eslint-disable no-undef */
interface PrivateSigningKey {
interface P2PSigningPrivateKey {
sign: (message: Buffer, callback: (err: ?Error, sig: Buffer) => void) => void,
public: PublicSigningKey,
public: P2PSigningPublicKey,
bytes: Buffer
}

interface PublicSigningKey {
interface P2PSigningPublicKey {
verify: (message: Buffer, signature: Buffer, callback: (err: ?Error, valid: boolean) => void) => void,
bytes: Buffer
}

export type PublisherId = {
id58: string,
privateKey: PrivateSigningKey
}
/* eslint-enable no-undef */

function generatePublisherId (): Promise<PublisherId> {
return new Promise((resolve, reject) => {
Crypto.generateKeyPair(PUBLISHER_KEY_TYPE, PUBLISHER_KEY_BITS,
(err: ?Error, privateKey: PrivateSigningKey) => { // eslint-disable-line no-undef
// wrapper classes for the above to expose a Promise-based interface
class PrivateSigningKey {
publicKey: PublicSigningKey
_key: P2PSigningPrivateKey // eslint-disable-line no-undef

constructor (p2pKey: P2PSigningPrivateKey) { // eslint-disable-line no-undef
this._key = p2pKey
this.publicKey = new PublicSigningKey(p2pKey.public)
}

static load (filename: string): Promise<PrivateSigningKey> {
return fs.readFile(filename)
.then(bytes => Crypto.unmarshalPrivateKey(bytes))
.then(p2pKey => new PrivateSigningKey(p2pKey))
}

static fromB58String (str: string): Promise<PrivateSigningKey> {
const bytes = Buffer.from(b58.decode(str))
return Crypto.unmarshalPrivateKey(bytes)
.then(p2pKey => new PrivateSigningKey(p2pKey))
}

save (filename: string): Promise<void> {
return fs.writeFile(filename, this.bytes)
}

get bytes (): Buffer {
return this._key.bytes
}

toB58String (): string {
return b58.encode(this.bytes)
}

sign (message: Buffer): Promise<Buffer> {
return new Promise((resolve, reject) => {
this._key.sign(message, (err, sig) => {
if (err) return reject(err)
const id58 = b58.encode(privateKey.public.bytes)
resolve({
id58,
privateKey
})
resolve(sig)
})
})
})
}
}

function publisherKeyFromB58String (key58: string): PublicSigningKey { // eslint-disable-line no-undef
const bytes = Buffer.from(b58.decode(key58))
return Crypto.unmarshalPublicKey(bytes)
}
class PublicSigningKey {
_key: P2PSigningPublicKey // eslint-disable-line no-undef

function publisherKeyToB58String (key: PublicSigningKey): string { // eslint-disable-line no-undef
return b58MultihashForBuffer(key.bytes)
}
constructor (p2pKey: P2PSigningPublicKey) { // eslint-disable-line no-undef
this._key = p2pKey
}

static load (filename: string): Promise<PublicSigningKey> {
return fs.readFile(filename)
.then(bytes => Crypto.unmarshalPublicKey(bytes))
.then(p2pKey => new PublicSigningKey(p2pKey))
}

static fromB58String (b58String: string): PublicSigningKey {
const bytes = Buffer.from(b58.decode(b58String))
const p2pKey = Crypto.unmarshalPublicKey(bytes)
return new PublicSigningKey(p2pKey)
}

function signBuffer (
key: PrivateSigningKey, // eslint-disable-line no-undef
message: Buffer)
: Promise<Buffer> {
return new Promise((resolve, reject) => {
key.sign(message, (err, sig) => {
if (err) return reject(err)
resolve(sig)
save (filename: string): Promise<void> {
return fs.writeFile(filename, this.bytes)
}

get bytes (): Buffer {
return this._key.bytes
}

toB58String (): string {
return b58.encode(this.bytes)
}

verify (message: Buffer, signature: Buffer): Promise<boolean> {
return new Promise((resolve, reject) => {
this._key.verify(message, signature, (err, valid) => {
if (err) return reject(err)
resolve(valid)
})
})
})
}
}

function verifyBuffer (
key: PublicSigningKey, // eslint-disable-line no-undef
message: Buffer,
sig: Buffer)
: Promise<boolean> {
return new Promise((resolve, reject) => {
key.verify(message, sig, (err, valid) => {
if (err) return reject(err)
resolve(valid)
})
})
class PublisherId {
id58: string
privateKey: PrivateSigningKey

constructor (privateKey: PrivateSigningKey | P2PSigningPrivateKey) { // eslint-disable-line no-undef
if (!(privateKey instanceof PrivateSigningKey)) {
privateKey = new PrivateSigningKey(privateKey)
}

this.id58 = b58.encode(privateKey.publicKey.bytes)
this.privateKey = privateKey
}

static generate (): Promise<PublisherId> {
return Crypto.generateKeyPair(PUBLISHER_KEY_TYPE, PUBLISHER_KEY_BITS)
.then(key => new PublisherId(key))
}

static load (filename: string): Promise<PublisherId> {
return PrivateSigningKey.load(filename)
.then(key => new PublisherId(key))
}

save (filename: string): Promise<void> {
return this.privateKey.save(filename)
}

sign (message: Buffer): Promise<Buffer> {
return this.privateKey.sign(message)
}

verify (message: Buffer, signature: Buffer): Promise<boolean> {
return this.privateKey.publicKey.verify(message, signature)
}
}

module.exports = {
Expand All @@ -163,9 +230,7 @@ module.exports = {
loadIdentity,
loadOrGenerateIdentity,
inflateMultiaddr,
generatePublisherId,
publisherKeyFromB58String,
publisherKeyToB58String,
signBuffer,
verifyBuffer
PublisherId,
PublicSigningKey,
PrivateSigningKey
}
4 changes: 2 additions & 2 deletions src/peer/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { verifyStatementWithKeyCache } = require('../metadata/signatures')

import type { MediachainNode } from './node'
import type { Datastore } from './datastore'
import type { PublicSigningKey } from './identity'
import type { P2PSigningPublicKey } from './identity'
import type { PullStreamSource, PullStreamThrough } from './util'
import type { QueryResultMsg, QueryResultValueMsg, StreamErrorMsg, StatementMsg, DataRequestMsg, DataObjectMsg } from '../protobuf/types'
import type { Connection } from 'interface-connection'
Expand All @@ -29,7 +29,7 @@ function mergeFromStreams (
queryResultStream: PullStreamSource<QueryResultMsg>,
dataConn: Connection)
: Promise<MergeResult> {
const publisherKeyCache: Map<string, PublicSigningKey> = new Map()
const publisherKeyCache: Map<string, P2PSigningPublicKey> = new Map()
const objectIdStream = pushable()
const objectIngestionErrors: Array<string> = []
const statementIngestionErrors: Array<string> = []
Expand Down
8 changes: 4 additions & 4 deletions test/merge_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ const { PROTOCOLS } = require('../src/peer/constants')
const { b58MultihashForBuffer } = require('../src/common/util')
const { makeSimpleStatement } = require('../src/metadata/statement')
const serialize = require('../src/metadata/serialize')
const { generatePublisherId } = require('../src/peer/identity')
const { PublisherId } = require('../src/peer/identity')

import type { PublisherId } from '../src/peer/identity'
import type { QueryResultMsg, StatementMsg } from '../src/protobuf/types'

const TEST_NAMESPACE = 'scratch.merge-test'
Expand Down Expand Up @@ -48,7 +47,7 @@ describe('Merge', () => {
before(() =>
makeNode()
.then(node => { alephNode = node })
.then(() => generatePublisherId())
.then(() => PublisherId.generate())
.then(pubId => { publisherId = pubId })
.then(() => makeSeedStatements(publisherId, SEED_OBJECT_BUFFERS))
.then(statements => { seedStatements = statements })
Expand All @@ -66,8 +65,9 @@ describe('Merge', () => {
.then(() => mockSource.start())
.then(() => alephNode.merge(mockSource.peerInfo, `SELECT * FROM ${TEST_NAMESPACE}`))
.then(result => {
console.log('merge result: ', result)
assert.notEqual(result, null, 'merge did not return a result')
assert.equal(result.statementCount, seedStatements.length, 'merged an unexpected number of statements')
assert.equal(result.objectCount, SEED_OBJECT_BUFFERS.length, 'merged an unexpected number of objects')
})
.catch(err => {
console.error('error during merge test: ', err)
Expand Down
6 changes: 6 additions & 0 deletions test/resources/fixtures/concat-message-signature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
publisherIdB58: '4XTTM4JKrrBeAK6qXmo8FoKmT5RkfjeXfZrnWjJNw9fKvPnEs',
privateKeyB58: 'K3TgUMffighKb23qdfq8oyJM4ByiG3nMgKMHNQeTFEKtfDJzGaTeEzJzp1BUmPYikTtzrgyiEg8DGSfWXemtWh5APs2a8K8hpfHYwhmvxh35YdGrrYHgMEpxFxCEEiaRkN7oDt7q',
message: Buffer.from('This message was signed with a key generated by concat', 'utf-8'),
signature: Buffer.from('Id02py5k9yGJBYQ6/W1qOThAdF9Y5LQySQGbCQdV+pYou+xNeRG26sIJQCZ3B/9SQ/CpJfZjwr+QjwydNWg6Ag==', 'base64')
}
19 changes: 19 additions & 0 deletions test/resources/generate_test_publisher_ids.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const path = require('path')
const mkdirp = require('mkdirp')
const {PublisherId} = require('../../lib/peer/identity')

const NUM_IDS = 20
const outputDir = path.join(__dirname, 'publisher_ids')
mkdirp.sync(outputDir)

const promises = []
for (let i = 0; i < NUM_IDS; i++) {
promises.push(
PublisherId.generate().then(id =>
id.save(path.join(outputDir, `${id.id58}.id`)))
)
}

Promise.all(promises).then(ids => {
console.log(`wrote ${ids.length} ids to ${outputDir}`)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`���\t�N���ia�ɥ6u&"��ӿ����E*�/�}������*#�W�؊���(��բ4��/�}������*#�W�؊���(��բ4�
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`�4̌6�F���E���Q��K���X+_��O�'m�Z4I�����7}:�9�%��'��Y^�>�>TZ4I�����7}:�9�%��'��Y^�>�>T
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`�-N����xV�څ��]�N�lSP]u��]�F'xO40r��e��v�Ìw*���A7;�YO40r��e��v�Ìw*���A7;�Y
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`�`�Q8➭��)�^=ˢ��Yh�d��[ф`��O=E���JL�4��­�,^3P��K��겑�.�O=E���JL�4��­�,^3P��K��겑�.�
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
`������4�s����G����
�3<�Z��rٌ �aJ�H�ɦge���qkZ�y��6��F>R��aJ�H�ɦge���qkZ�y��6��F>R��
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`�Ibe� �(.�3md>=�u�?�7H��5��Î"������@^�7�$YB `�ъA�.z~ B�"������@^�7�$YB `�ъA�.z~ B
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
`�
�E��ZΦ,t!M�j��h��B#���ג�x�f�Z`��]�� ��� ~궱�ML=���x�f�Z`��]�� ��� ~궱�ML=�
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`��f��|��S5�rB��� ֮I�s�Qs4�Gm��4��)?�[���shN8�CuB����a��˰�4��)?�[���shN8�CuB����a��˰
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`Iu^����L���Ly�g�9� O�܋ݹ'�ž;��vv�-VI�Z��8=�g��T��읟q�ž;��vv�-VI�Z��8=�g��T��읟q
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
` �(F�Ҋ�Ď� P��m�Ft����aa�m+@���Wt��#�7�ͽ��e�`�A�u��Y����q��Wt��#�7�ͽ��e�`�A�u��Y����q
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
`/��r�l� �4
!���fU7���@�@��>�EϏ��s�W�Ȝ����=��u�m��n�'��Ϗ��s�W�Ȝ����=��u�m��n�'��

0 comments on commit f0b0936

Please sign in to comment.