blockchain-link
Version:
Link - The Blockchain File Sharing Protocol
332 lines (306 loc) • 10.9 kB
text/coffeescript
crypto = require("crypto")
BigInteger = require("jsbn")
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
nbi = ->
new BigInteger(null)
nbv = (i) ->
r = nbi()
r.fromInt i
r
BigInteger.valueOf = nbv
base = BigInteger.valueOf(58)
BigInteger.fromByteArrayUnsigned = (ba) ->
unless ba.length
ba.valueOf 0
else if ba[0] & 0x80
new BigInteger([0].concat(ba))
else
new BigInteger(ba)
BigInteger::toByteArrayUnsigned = ->
ba = .toByteArray()
if ba.length
ba = ba.slice(1) if ba[0] is 0
ba.map (v) ->
(if (v < 0) then v + 256 else v)
else
ba
hexToBytes = (hex) ->
bytes = []
c = 0
while c < hex.length
bytes.push parseInt(hex.substr(c, 2), 16)
c += 2
bytes
bytesToHex = (bytes) ->
hex = []
i = 0
while i < bytes.length
hex.push (bytes[i] >>> 4).toString(16)
hex.push (bytes[i] & 0xF).toString(16)
i++
hex.join ""
encodeBase58 = (input) ->
bi = BigInteger.fromByteArrayUnsigned(input)
chars = []
while bi.compareTo(base) >= 0
mod = bi.mod(base)
chars.unshift alphabet[mod.intValue()]
bi = bi.subtract(mod).divide(base)
chars.unshift alphabet[bi.intValue()]
i = 0
while i < input.length
if input[i] is 0x00
chars.unshift alphabet[0]
else
break
i++
chars.join ""
decodeBase58 = (input) ->
bi = BigInteger.valueOf(0)
leadingZerosNum = 0
i = input.length - 1
while i >= 0
alphaIndex = alphabet.indexOf(input[i])
throw "Invalid character" if alphaIndex < 0
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(base.pow(input.length - 1 - i)))
# This counts leading zero bytes
if input[i] is "1"
leadingZerosNum++
else
leadingZerosNum = 0
i--
bytes = bi.toByteArrayUnsigned()
# Add leading zeros
bytes.unshift 0 while leadingZerosNum-- > 0
bytes
decimalToHex = (d, padding) ->
hex = Number(d).toString(16)
padding = (if typeof (padding) is "undefined" or padding is null then 2 else padding)
hex = "0" + hex while hex.length < padding
hex
hashBuffer = (algo, buffer) ->
hash = crypto.createHash algo
hash.update buffer
hash.digest()
encodeAddress = (buf, version) ->
version = version or 0x00
padding = new Buffer(21) # version byte + 20 bytes = 21
padding.fill version # fill with version
buf.copy padding, 1, 0 # start a 1 to leave version byte at start
twice = hashBuffer "sha256", hashBuffer "sha256", padding
fin = new Buffer(25) # version byte + 20 bytes + 4 byte checksum = 25
padding.copy fin
twice.copy fin, 21 # checksum starts at 21 which is version byte + 20 bytes
encodeBase58 hexToBytes(fin.toString("hex"))
encodeAddresses = (buf, version) ->
version = version or 0x00
result = []
x = 0
while x < buf.length
next = buf.slice(x, x + 20) # get the next 20 bytes
result.push encodeAddress(next, version)
x = x + 20
result
opCodes =
startSequenceOpCode: "4c696e6b"
inlinePayloadOpCode: "01"
attachmentPayloadOpCode: "02"
mimeTypeOpCode: "03"
payloadEncodingOpCode: "04"
payloadMD5OpCode: "05"
payloadSHA1OpCode: "06"
payloadSHA256OpCode: "07"
nameOpCode: "10"
descriptionOpCode: "11"
keywordsOpCode: "12"
uriOpCode: "13"
filenameOpCode: "14"
originalCreationDateOpCode: "15"
lastModifiedDateOpCode: "16"
licenseOpCode: "17"
referencesTransactionOpCode: "F1"
replacesTransactionOpCode: "F2"
nextTransactionOpCode: "FF"
endSequence: "00"
class LinkSequenceBuilder
constructor: ( ) ->
str: opCodes.startSequenceOpCode
toString: ->
+ opCodes.endSequence
addName: (name) ->
+=
addDescription: (description) ->
+= description
addURI: (uri) ->
+= uri
addFilename: (filename) ->
+= filename
addMimeType: (mimeType) ->
+= mimeType
addKeywords: (keywords) ->
+= keywords
addOriginalCreationDate: (date) ->
+= date
addLastModifiedDate: (date) ->
+= date
addPayloadInline: (payload) ->
+= payload
addPayloadAttachment: (buf) ->
+= buf
addPayloadMD5: (buf) ->
+= buf
addPayloadSHA1: (buf) ->
+= buf
addPayloadSHA256: (buf) ->
+= buf
addLicense:(license)->
+= license
getAddresses: () ->
encodeAddresses new Buffer( , "hex"),
encodeBuffer: (buf) ->
decimalToHex(buf.length, 4) + buf.toString("hex");
encodeString: (str) ->
encodePayloadInline: (str) ->
opCodes.inlinePayloadOpCode +
encodePayloadAttachment: (buf) ->
opCodes.attachmentPayloadOpCode +
encodePayloadEncoding: (str) ->
opCodes.payloadEncodingOpCode +
encodePayloadMD5Buffer: (buf) ->
opCodes.payloadMD5OpCode + hashBuffer("md5", buf).toString "hex"
encodePayloadSHA1Buffer: (buf) ->
opCodes.payloadSHA1OpCode + hashBuffer("sha1", buf).toString "hex"
encodePayloadSHA256Buffer: (buf) ->
opCodes.payloadSHA256OpCode + hashBuffer("sha256", buf).toString "hex"
encodeName: (str) ->
opCodes.nameOpCode +
encodeDescription: (str) ->
opCodes.descriptionOpCode +
encodeURI: (str) ->
opCodes.uriOpCode +
encodeFilename: (str) ->
opCodes.filenameOpCode +
encodeKeywords: (str) ->
opCodes.keywordsOpCode +
encodeMimeType: (str) ->
opCodes.mimeTypeOpCode +
encodeOriginalCreationDate: (date) ->
opCodes.originalCreationDateOpCode + decimalToHex(date.getTime(), 12)
encodeLastModifiedDate: (date) ->
opCodes.lastModifiedDateOpCode + decimalToHex(date.getTime(), 12)
encodeLicense: (license) ->
opcodes.licenseOpCode +
class LinkSequenceDecoder
decode: (addresses) ->
sequence = new Buffer(addresses.length * 20)
sequence.fill 0x00
for x of addresses
new Buffer(bytesToHex(decodeBase58(addresses[x]).slice(1)), "hex").copy sequence, x * 20
startSequence= new Buffer(4)
sequence.copy startSequence
firstFour = startSequence.toString("utf-8")
throw "First 4 bytes were: " + firstFour unless firstFour is "Link"
ip = 4
running = true
result = {}
while running
nextOp = new Buffer(1)
sequence.copy nextOp, 0, ip
op = nextOp.toString("hex")
ip++
switch op
when opCodes.inlinePayloadOpCode
payload =
ip += payload[0]
result.payloadInline = payload[1]
when opCodes.nameOpCode
payload =
ip += payload[0]
result.name = payload[1]
when opCodes.keywordsOpCode
payload =
ip += payload[0]
result.keywords = payload[1]
when opCodes.descriptionOpCode
payload =
ip += payload[0]
result.description = payload[1]
when opCodes.uriOpCode
payload =
ip += payload[0]
result.URI = payload[1]
when opCodes.filenameOpCode
payload =
ip += payload[0]
result.filename = payload[1]
when opCodes.attachmentPayloadOpCode
payload =
ip += payload[0]
result.payloadAttachment = payload[1].toString("hex")
when opCodes.payloadMD5OpCode
payload =
ip += payload[0]
result.payloadMD5 = payload[1].toString("hex")
when opCodes.payloadSHA1OpCode
payload =
ip += payload[0]
result.payloadSHA1 = payload[1].toString("hex")
when opCodes.payloadSHA256OpCode
payload =
ip += payload[0]
result.payloadSHA256 = payload[1].toString("hex")
when opCodes.originalCreationDateOpCode
result.originalCreationDate =
ip += 6
when opCodes.lastModifiedDateOpCode
result.lastModifiedDate =
ip += 6;
when opCodes.licenseOpCode
payload =
ip += payload[0]
result.license = payload[1]
when opCodes.endSequence
running = false
result
verify: (result)->
errors = []
p = result.payloadAttachment or result.payloadInline;
if not p? then return
if result.payloadMD5?
h = hashBuffer "md5", p
if(h.toString("hex") != result.payloadMD5)
errors.push("Expected MD5 was #{result.payloadMD5} but the payload MD5 is #{h}")
if result.payloadSHA1?
h = hashBuffer "sha1", p
if(h.toString("hex") != result.payloadSHA1)
errors.push("Expected SHA-1 was #{result.payloadSHA1} but the payload SHA-1 is #{h}")
if result.payloadSHA256?
h = hashBuffer "sha256", p
if(h.toString("hex") != result.payloadSHA256)
errors.push("Expected SHA-256 was #{result.payloadSHA256} but the payload SHA-256 is #{h}")
errors
decodeSize: (buffer, ip) ->
parseInt [1].toString("hex"), 16
decodeString: (buffer, ip) ->
size =
[size + 2, [1].toString("utf-8")]
decodeBuffer: (buffer, ip) ->
size = decodeSize(buffer, ip)
[size + 2, [1]]
decodeBytes: (buffer, ip, length) ->
p = new Buffer(length)
buffer.copy p, 0, ip
[length, p]
decodeDate: (buffer, ip) ->
buf = new Buffer 6
buffer.copy buf, 0, ip
d = new Date parseInt buf.toString("hex"), 16
if exports?
exports.LinkSequenceBuilder = module.exports.LinkSequenceBuilder = LinkSequenceBuilder;
exports.LinkSequenceEncoder = module.exports.LinkSequenceDecoder = LinkSequenceDecoder;
exports.opCodes = module.exports.opCodes = opCodes;
exports.decodeBase58 = decodeBase58
exports.encodeBase58 = encodeBase58
exports.bytesToHex = bytesToHex
exports.hashBuffer = hashBuffer