Skip to content
This repository has been archived by the owner on Feb 24, 2021. It is now read-only.

Commit

Permalink
fix: replace node buffers with uint8arrays (#124)
Browse files Browse the repository at this point in the history
* fix: replace node buffers with uint8arrays

All use of node buffers has been replaced with uint8arrays

All deps have been updated to ones that use uint8arrays

BREAKING CHANGES:

- The dependencies this module has have uint8arrays as properties
  instead of node buffers

* chore: remove buffer api usage from tests
  • Loading branch information
achingbrain authored Aug 10, 2020
1 parent 328e68f commit c3f1f34
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 55 deletions.
2 changes: 1 addition & 1 deletion benchmarks/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async function sendData (a, b, opts) {
pipe(
function * () {
while (i--) {
yield Buffer.allocUnsafe(opts.size)
yield new Uint8Array(opts.size)
}
},
a
Expand Down
19 changes: 9 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,18 @@
"it-pair": "^1.0.0",
"it-pb-rpc": "^0.1.4",
"it-pipe": "^1.1.0",
"libp2p-crypto": "^0.17.3",
"libp2p-interfaces": "^0.2.1",
"multiaddr": "^7.2.1",
"multihashing-async": "^0.8.0",
"peer-id": "^0.13.6",
"protons": "^1.0.2"
"libp2p-crypto": "^0.18.0",
"libp2p-interfaces": "^0.3.2",
"multiaddr": "^8.0.0",
"multihashing-async": "^2.0.1",
"peer-id": "^0.14.0",
"protons": "^2.0.0",
"uint8arrays": "^1.1.0"
},
"devDependencies": {
"aegir": "^22.0.0",
"aegir": "^25.0.0",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"dirty-chai": "^2.0.1",
"streaming-iterables": "^4.1.1"
"streaming-iterables": "^5.0.2"
},
"engines": {
"node": ">=6.0.0",
Expand Down
6 changes: 4 additions & 2 deletions src/etm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const BufferList = require('bl/BufferList')
const { InvalidCryptoTransmissionError } = require('libp2p-interfaces/src/crypto/errors')
const uint8ArrayToString = require('uint8arrays/to-string')
const uint8ArrayEquals = require('uint8arrays/equals')

exports.createBoxStream = (cipher, mac) => {
return async function * (source) {
Expand Down Expand Up @@ -29,8 +31,8 @@ exports.createUnboxStream = (decipher, mac) => {

const expected = await mac.digest(data)

if (!macd.equals(expected)) {
throw new InvalidCryptoTransmissionError(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`)
if (!uint8ArrayEquals(macd, expected)) {
throw new InvalidCryptoTransmissionError(`MAC Invalid: ${uint8ArrayToString(macd, 'base16')} != ${uint8ArrayToString(expected, 'base16')}`)
}

const decrypted = await decipher.decrypt(data)
Expand Down
14 changes: 8 additions & 6 deletions src/handshake/crypto.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use strict'

const { Buffer } = require('buffer')
const PeerId = require('peer-id')
const crypto = require('libp2p-crypto')
const debug = require('debug')
const uint8ArrayConcat = require('uint8arrays/concat')
const uint8ArrayEquals = require('uint8arrays/equals')
const uint8ArrayToString = require('uint8arrays/to-string')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')

Expand Down Expand Up @@ -36,7 +38,7 @@ exports.createExchange = async (state) => {
state.shared.generate = res.genSharedKey

// Gather corpus to sign.
const selectionOut = Buffer.concat([
const selectionOut = uint8ArrayConcat([
state.proposalEncoded.out,
state.proposalEncoded.in,
state.ephemeralKey.local
Expand All @@ -61,7 +63,7 @@ exports.identify = async (state, msg) => {

state.key.remote = crypto.keys.unmarshalPublicKey(pubkey)

const remoteId = await PeerId.createFromPubKey(pubkey.toString('base64'))
const remoteId = await PeerId.createFromPubKey(uint8ArrayToString(pubkey, 'base64pad'))

// If we know who we are dialing to, double check
if (state.id.remote) {
Expand Down Expand Up @@ -120,7 +122,7 @@ exports.verify = async (state, msg) => {
state.exchange.in = pbm.Exchange.decode(msg)
state.ephemeralKey.remote = state.exchange.in.epubkey

const selectionIn = Buffer.concat([
const selectionIn = uint8ArrayConcat([
state.proposalEncoded.in,
state.proposalEncoded.out,
state.ephemeralKey.remote
Expand Down Expand Up @@ -168,9 +170,9 @@ exports.generateKeys = async (state) => {
exports.verifyNonce = (state, n2) => {
const n1 = state.proposal.out.rand

if (n1.equals(n2)) return
if (uint8ArrayEquals(n1, n2)) return

throw new Error(
`Failed to read our encrypted nonce: ${n1.toString('hex')} != ${n2.toString('hex')}`
`Failed to read our encrypted nonce: ${uint8ArrayToString(n1, 'base16')} != ${uint8ArrayToString(n2, 'base16')}`
)
}
9 changes: 5 additions & 4 deletions src/support.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict'

const { Buffer } = require('buffer')
const mh = require('multihashing-async')
const crypto = require('libp2p-crypto')
const uint8ArrayConcat = require('uint8arrays/concat')
const uint8ArrayCompare = require('uint8arrays/compare')

const { InvalidCryptoExchangeError } = require('libp2p-interfaces/src/crypto/errors')

Expand Down Expand Up @@ -69,16 +70,16 @@ function makeCipher (cipherType, iv, key) {
}

exports.selectBest = async (local, remote) => {
const oh1 = await exports.digest(Buffer.concat([
const oh1 = await exports.digest(uint8ArrayConcat([
remote.pubKeyBytes,
local.nonce
]))
const oh2 = await exports.digest(Buffer.concat([
const oh2 = await exports.digest(uint8ArrayConcat([
local.pubKeyBytes,
remote.nonce
]))

const order = Buffer.compare(oh1, oh2)
const order = uint8ArrayCompare(oh1, oh2)

if (order === 0) {
throw new InvalidCryptoExchangeError('you are trying to talk to yourself')
Expand Down
76 changes: 48 additions & 28 deletions test/secio.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
/* eslint-env mocha */
'use strict'

const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const { expect } = require('aegir/utils/chai')

const PeerId = require('peer-id')
const duplexPair = require('it-pair/duplex')
Expand All @@ -22,6 +19,7 @@ const {
const { createBoxStream, createUnboxStream } = require('../src/etm')
const State = require('../src/state')
const { Propose } = require('../src/handshake/secio.proto')
const uint8ArrayConcat = require('uint8arrays/concat')

describe('secio', () => {
let remotePeer
Expand All @@ -43,13 +41,16 @@ describe('secio', () => {
const proposal = createProposal(state)

// Send our proposal
const proposalLength = Buffer.allocUnsafe(4)
proposalLength.writeInt32BE(proposal.length, 0)
wrap.write(Buffer.concat([proposalLength, proposal]))
const proposalBuffer = new ArrayBuffer(4)
const proposalLengthView = new DataView(proposalBuffer)
proposalLengthView.setInt32(0, proposal.length)
const proposalLength = new Uint8Array(proposalBuffer)
wrap.write(uint8ArrayConcat([proposalLength, proposal]))

// Read their proposal
let theirProposalRaw = (await wrap.read()).slice()
let dataLength = theirProposalRaw.readInt32BE(0)
const theirProposalRawView = new DataView(theirProposalRaw.buffer, theirProposalRaw.byteOffset, theirProposalRaw.byteLength)
let dataLength = theirProposalRawView.getInt32(0)
theirProposalRaw = theirProposalRaw.slice(4, dataLength + 4)
const theirProposal = Propose.decode(theirProposalRaw)
expect(theirProposal.rand).to.have.length(16)
Expand All @@ -68,13 +69,16 @@ describe('secio', () => {
const exchange = await createExchange(state)

// Send our exchange
const exchangeLength = Buffer.allocUnsafe(4)
exchangeLength.writeInt32BE(exchange.length, 0)
wrap.write(Buffer.concat([exchangeLength, exchange]))
const exchangeBuffer = new ArrayBuffer(4)
const exchangeLengthView = new DataView(exchangeBuffer)
exchangeLengthView.setInt32(0, exchange.length)
const exchangeLength = new Uint8Array(exchangeBuffer)
wrap.write(uint8ArrayConcat([exchangeLength, exchange]))

// Read their exchange
let theirExchangeRaw = (await wrap.read()).slice()
dataLength = theirExchangeRaw.readInt32BE(0)
const theirExchangeRawView = new DataView(theirExchangeRaw.buffer, theirExchangeRaw.byteOffset, theirExchangeRaw.byteLength)
dataLength = theirExchangeRawView.getInt32(0)
theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4)
await verify(state, theirExchangeRaw)

Expand All @@ -88,13 +92,18 @@ describe('secio', () => {
// Send back their nonce over the crypto stream
const { value: nonce } = await box([state.proposal.in.rand]).next()
expect(nonce.slice()).to.not.eql(state.proposal.in.rand) // The nonce should be encrypted
const nonceLength = Buffer.allocUnsafe(4)
nonceLength.writeInt32BE(nonce.length, 0)
wrap.write(Buffer.concat([nonceLength, nonce.slice()]))

const nonceBuffer = new ArrayBuffer(4)
const nonceView = new DataView(nonceBuffer)
nonceView.setInt32(0, nonce.length)
const nonceLength = new Uint8Array(nonceBuffer)
wrap.write(uint8ArrayConcat([nonceLength, nonce.slice()]))

// Read our nonce from the crypto stream
let ourNonceRaw = (await wrap.read())
dataLength = ourNonceRaw.readInt32BE(0)
const ourNonceRawBuffer = ourNonceRaw.slice()
const ourNonceRawView = new DataView(ourNonceRawBuffer.buffer, ourNonceRawBuffer.byteOffset, ourNonceRawBuffer.byteLength)
dataLength = ourNonceRawView.getInt32(0)
ourNonceRaw = ourNonceRaw.shallowSlice(4, dataLength + 4) // Unbox expects a BufferList, so shallow slice here
expect(ourNonceRaw.slice()).to.not.eql(state.proposal.out.rand) // The nonce should be encrypted
const { value: ourNonce } = await unbox([ourNonceRaw]).next()
Expand All @@ -121,13 +130,16 @@ describe('secio', () => {
const proposal = createProposal(state)

// Send our proposal
const proposalLength = Buffer.allocUnsafe(4)
proposalLength.writeInt32BE(proposal.length, 0)
wrap.write(Buffer.concat([proposalLength, proposal]))
const proposalBuffer = new ArrayBuffer(4)
const proposalLengthView = new DataView(proposalBuffer)
proposalLengthView.setInt32(0, proposal.length)
const proposalLength = new Uint8Array(proposalBuffer)
wrap.write(uint8ArrayConcat([proposalLength, proposal]))

// Read their proposal
let theirProposalRaw = (await wrap.read()).slice()
let dataLength = theirProposalRaw.readInt32BE(0)
const theirProposalRawView = new DataView(theirProposalRaw.buffer, theirProposalRaw.byteOffset, theirProposalRaw.byteLength)
let dataLength = theirProposalRawView.getInt32(0)
theirProposalRaw = theirProposalRaw.slice(4, dataLength + 4)
const theirProposal = Propose.decode(theirProposalRaw)
expect(theirProposal.rand).to.have.length(16)
Expand All @@ -146,13 +158,16 @@ describe('secio', () => {
const exchange = await createExchange(state)

// Send our exchange
const exchangeLength = Buffer.allocUnsafe(4)
exchangeLength.writeInt32BE(exchange.length, 0)
wrap.write(Buffer.concat([exchangeLength, exchange]))
const exchangeBuffer = new ArrayBuffer(4)
const exchangeLengthView = new DataView(exchangeBuffer)
exchangeLengthView.setInt32(0, exchange.length)
const exchangeLength = new Uint8Array(exchangeBuffer)
wrap.write(uint8ArrayConcat([exchangeLength, exchange]))

// Read their exchange
let theirExchangeRaw = (await wrap.read()).slice()
dataLength = theirExchangeRaw.readInt32BE(0)
const theirExchangeRawView = new DataView(theirExchangeRaw.buffer, theirExchangeRaw.byteOffset, theirExchangeRaw.byteLength)
dataLength = theirExchangeRawView.getInt32(0)
theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4)
await verify(state, theirExchangeRaw)

Expand All @@ -166,13 +181,18 @@ describe('secio', () => {
// Send back their nonce over the crypto stream
const { value: nonce } = await box([state.proposal.in.rand]).next()
expect(nonce.slice()).to.not.eql(state.proposal.in.rand) // The nonce should be encrypted
const nonceLength = Buffer.allocUnsafe(4)
nonceLength.writeInt32BE(nonce.length, 0)
wrap.write(Buffer.concat([nonceLength, nonce.slice()]))

const nonceBuffer = new ArrayBuffer(4)
const nonceView = new DataView(nonceBuffer)
nonceView.setInt32(0, nonce.length)
const nonceLength = new Uint8Array(nonceBuffer)
wrap.write(uint8ArrayConcat([nonceLength, nonce.slice()]))

// Read our nonce from the crypto stream
let ourNonceRaw = (await wrap.read())
dataLength = ourNonceRaw.readInt32BE(0)
const ourNonceRawBuffer = ourNonceRaw.slice()
const ourNonceRawView = new DataView(ourNonceRawBuffer.buffer, ourNonceRawBuffer.byteOffset, ourNonceRawBuffer.byteLength)
dataLength = ourNonceRawView.getInt32(0)
ourNonceRaw = ourNonceRaw.shallowSlice(4, dataLength + 4) // Unbox expects a BufferList, so shallow slice here
expect(ourNonceRaw.slice()).to.not.eql(state.proposal.out.rand) // The nonce should be encrypted
const { value: ourNonce } = await unbox([ourNonceRaw]).next()
Expand Down
5 changes: 1 addition & 4 deletions test/support.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
/* eslint-env mocha */
'use strict'

const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const { expect } = require('aegir/utils/chai')

const support = require('../src/support')

Expand Down

0 comments on commit c3f1f34

Please sign in to comment.