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

fix: replace node buffers with uint8arrays #124

Merged
merged 2 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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