-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: structured value, safe set/get
- Loading branch information
Showing
12 changed files
with
455 additions
and
1,707 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const fs = require('fs'); | ||
|
||
class RootCache { | ||
constructor() { | ||
this.ROOT_FN = ".root"; | ||
} | ||
get() { | ||
let rootBinary = fs.readFileSync(this.ROOT_FN); | ||
return JSON.parse(rootBinary); | ||
} | ||
set(root) { | ||
fs.writeFileSync(this.ROOT_FN, JSON.stringify(root)); | ||
} | ||
} | ||
|
||
module.exports = RootCache; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
const RootCache = require('./rootCache'); | ||
|
||
class RootService { | ||
constructor(immuClient) { | ||
this.immuClient = immuClient; | ||
this.cache = new RootCache(); | ||
} | ||
init(onComplete) { | ||
this.immuClient.currentRoot({}, (err, response) => { | ||
if (err) { | ||
throw "Root not found"; | ||
} | ||
|
||
this.cache.set(response); | ||
|
||
if (onComplete) | ||
onComplete(response); | ||
}); | ||
} | ||
getRoot(onComplete) { | ||
try { | ||
let root = this.cache.get(); | ||
onComplete(root); | ||
} catch (_) { | ||
this.immuClient.currentRoot({}, (err, response) => { | ||
if (err) { | ||
throw "Root not found"; | ||
} | ||
|
||
this.cache.set(response); | ||
onComplete(response); | ||
}); | ||
} | ||
} | ||
|
||
setRoot(value) { | ||
this.cache.set(value); | ||
} | ||
}; | ||
|
||
module.exports = RootService; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
const itemUtils = require("./../utils/item"); | ||
const Proofs = require("./../utils/proofs"); | ||
|
||
const call = (immuClient, rootService, request, callback) => { | ||
rootService.getRoot((root) => { | ||
let index = { index: root.index }; | ||
let protoReq = { | ||
key: { key: Buffer.from(request.key) }, | ||
rootIndex: index | ||
}; | ||
|
||
immuClient._safeGet(protoReq, (err, msg) => { | ||
if (err) { | ||
callback(err, null); | ||
return; | ||
} | ||
|
||
let verified = Proofs.verify(msg.proof, itemUtils.hash(msg.item), root); | ||
|
||
if (verified) { | ||
let toCache = { | ||
index: msg.proof.at, | ||
root: msg.proof.root | ||
}; | ||
|
||
rootService.setRoot(toCache); | ||
} | ||
|
||
let i = msg.item; | ||
let valueRaw = Buffer.from(i.value); | ||
|
||
callback(null, { | ||
index: i.index, | ||
key: i.key, | ||
value: valueRaw.subarray(8), | ||
timestamp: Number(valueRaw.slice(0, 8).readBigUInt64BE()), | ||
verified | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
module.exports.call = call; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
const itemUtils = require("./../utils/item"); | ||
const Proofs = require("./../utils/proofs"); | ||
|
||
const call = (immuClient, rootService, request, callback) => { | ||
rootService.getRoot((root) => { | ||
let index = { index: root.index }; | ||
var valueB = Buffer.alloc(8 + request.kv.value.length); | ||
|
||
var buffTimestamp = Buffer.alloc(8); | ||
buffTimestamp.writeBigUInt64BE(BigInt(Date.now())); | ||
buffTimestamp.copy(valueB, 0); | ||
|
||
var buffValue = Buffer.from(request.kv.value); | ||
buffValue.copy(valueB, 8); | ||
|
||
let protoReq = { | ||
kv: { | ||
key: Buffer.from(request.kv.key), | ||
value: valueB | ||
}, | ||
rootIndex: index | ||
}; | ||
|
||
immuClient._safeSet(protoReq, (err, msg) => { | ||
if (err) { | ||
callback(err, null); | ||
return; | ||
} | ||
|
||
let item = { | ||
index: msg.index, | ||
key: protoReq.kv.key, | ||
value: protoReq.kv.value | ||
}; | ||
|
||
if (Buffer.compare(itemUtils.hash(item), msg.leaf) != 0) | ||
throw "Proof does not match the given item"; | ||
|
||
let verified = Proofs.verify(msg, msg.leaf, root); | ||
|
||
if (verified) { | ||
let toCache = { index: msg.index, root: msg.root }; | ||
rootService.setRoot(toCache); | ||
} | ||
|
||
callback(null, { | ||
index: msg.index, | ||
leaf: msg.leaf, | ||
root: msg.root, | ||
at: msg.at, | ||
inclusionPath: msg.inclusionPath, | ||
consistencyPath: msg.consistencyPath, | ||
verified | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
module.exports.call = call; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,160 +1,57 @@ | ||
crypto = require ("crypto"); | ||
const proofs = require("./utils/proofs"); | ||
const digest = require("./utils/digest"); | ||
|
||
module.exports.inclusionVerify = function (path, at, i, root, leaf) { | ||
return _inclusionVerify(path, at, i, root, leaf) | ||
module.exports.inclusionVerify = function (path, at, i, root, leaf) { | ||
return proofs.inclusionVerify(path, at, i, root, leaf) | ||
}; | ||
|
||
module.exports.consistencyVerify = function (path, at, i, root, leaf) { | ||
return _consistencyVerify(path, at, i, root, leaf) | ||
module.exports.consistencyVerify = function (path, at, i, root, leaf) { | ||
return proofs.consistencyVerify(path, at, i, root, leaf) | ||
}; | ||
|
||
module.exports.digest = function (index, key, value) { | ||
return _digest(index, key, value) | ||
module.exports.digest = function (index, key, value) { | ||
return digest(index, key, value); | ||
}; | ||
|
||
module.exports.client = function (immudbUrl) { | ||
module.exports.client = function (immudbUrl) { | ||
return _client(immudbUrl) | ||
}; | ||
|
||
const LeafPrefix = 0; | ||
const NodePrefix = 1; | ||
|
||
const SHA256_SIZE = 32 | ||
|
||
const BigInt0 = BigInt("0") | ||
const BigInt1 = BigInt("1") | ||
const Bigint2 = BigInt("2") | ||
|
||
function _consistencyVerify(path, second, first, secondHash, firstHash) { | ||
first = BigInt(first) | ||
second = BigInt(second) | ||
let l = path.length | ||
if (first == second && firstHash.compare(secondHash) == 0 && l == 0){ | ||
return true | ||
} | ||
if (first < second || l == 0){ | ||
return false | ||
} | ||
let pp = path.slice() | ||
if( _isPowerOfTwo(first + BigInt1)){ | ||
pp.push(firstHash) | ||
} | ||
let fn = first | ||
let sn = second | ||
|
||
while ((fn%Bigint2) == BigInt1){ | ||
fn >>= BigInt1 | ||
sn >>= BigInt1 | ||
} | ||
let fr = pp[0] | ||
let sr = pp[0] | ||
|
||
for (let k = 1; k < p.length; k++){ | ||
if (sn == BigInt0){ | ||
return false | ||
} | ||
if ((fn%Bigint2) == BigInt1 || fn == sn){ | ||
let tmp = Buffer.from([NodePrefix]) | ||
tmp.copy( p[k] , 1) | ||
|
||
tmp.copy( fr , 1+SHA256_SIZE) | ||
fr = Buffer.from( crypto.createHash('sha256').update(tmp).digest()) | ||
|
||
tmp.copy( sr , 1+SHA256_SIZE) | ||
sr = Buffer.from( crypto.createHash('sha256').update(tmp).digest()) | ||
|
||
while ((fn%Bigint2) == BigInt0 && fn != BigInt0){ | ||
fn >>= BigInt1 | ||
sn >>= BigInt1 | ||
} | ||
}else{ | ||
let tmp = Buffer.from([NodePrefix]) | ||
tmp.copy( sr , 1) | ||
|
||
tmp.copy( p[k] , 1+SHA256_SIZE) | ||
sr = Buffer.from( crypto.createHash('sha256').update(tmp).digest()) | ||
} | ||
fn >>= BigInt1 | ||
sn >>= BigInt1 | ||
} | ||
|
||
return fr.compare(firstHash) == 0 && sr.compare(secondHash) == 0 && sn == BigInt0 | ||
} | ||
|
||
function _isPowerOfTwo(x) { | ||
return (x!=BigInt0 && (x & (x-BigInt1)) == BigInt0) | ||
} | ||
|
||
function _inclusionVerify(p, at, i, root, leaf) { | ||
at = BigInt(at) | ||
i = BigInt(i) | ||
|
||
if (i > at || (at > 0 && p.length == 0)) { | ||
return false | ||
} | ||
|
||
let h = leaf | ||
|
||
for (let k = 0; k < p.length; k++){ | ||
let t = Buffer.from([NodePrefix]) | ||
|
||
if (i % BigInt("2") == 0 && i != at){ | ||
t.copy(h, 1) | ||
t.copy(p[k], 1+SHA256_SIZE) | ||
}else{ | ||
t.copy(p[k], 1) | ||
t.copy(h, 1+SHA256_SIZE) | ||
} | ||
const hash = crypto.createHash('sha256'); | ||
h = Buffer.from(hash.update(t).digest()) | ||
i = i/BigInt("2") | ||
at = at/BigInt("2") | ||
} | ||
return at == i && h.compare(root) == 0 | ||
function _client(immudbUrl) { | ||
if (!immudbUrl) | ||
return null; | ||
|
||
} | ||
const grpc = require("grpc"); | ||
const protoLoader = require("@grpc/proto-loader"); | ||
const path = require("path"); | ||
|
||
function _digest( index , key, value ) { | ||
index = index.toString() | ||
k = Buffer.from(key) | ||
v = Buffer.from(value) | ||
c = Buffer.alloc(1+8+8+k.length+v.length) | ||
c[0] = LeafPrefix | ||
buf_index = Buffer.alloc(8) | ||
buf_index.writeBigUInt64BE(BigInt(index)) | ||
buf_index.copy(c, 1) | ||
buf_key = Buffer.alloc(8) | ||
buf_key.writeBigUInt64BE(BigInt(k.length)) | ||
buf_key.copy(c, 1+8) | ||
k.copy(c, 1+8+8) | ||
v.copy(c, 1+8+8+k.length) | ||
const hash = crypto.createHash('sha256'); | ||
d = hash.update(c).digest() | ||
return Buffer.from(d) | ||
} | ||
const safeGet = require('./handler/safeGet'); | ||
const safeSet = require('./handler/safeSet'); | ||
const rootService = require('./client/rootService'); | ||
|
||
function _client(immudbUrl) { | ||
if (immudbUrl) { | ||
grpc = require('grpc'); | ||
protoLoader = require('@grpc/proto-loader'); | ||
|
||
const PROTO_PATH = require('path').resolve(__dirname + '/schema.proto'); | ||
|
||
const packageDefinition = protoLoader.loadSync( | ||
PROTO_PATH, | ||
{keepCase: true, | ||
const PROTO_PATH = path.resolve(__dirname + "/schema.proto"); | ||
const packageDefinition = protoLoader.loadSync( | ||
PROTO_PATH, | ||
{ | ||
keepCase: true, | ||
longs: String, | ||
enums: String, | ||
defaults: true, | ||
oneofs: true | ||
}); | ||
|
||
const immudb_schema = grpc.loadPackageDefinition(packageDefinition).immudb.schema; | ||
const client = new immudb_schema.ImmuService(immudbUrl, grpc.credentials.createInsecure()); | ||
|
||
return client; | ||
} else { | ||
return null; | ||
} | ||
|
||
const immudbSchema = grpc.loadPackageDefinition(packageDefinition).immudb.schema; | ||
const client = new immudbSchema.ImmuService(immudbUrl, grpc.credentials.createInsecure()); | ||
const rs = new rootService(client); | ||
|
||
rs.init(); | ||
|
||
client._safeGet = client.safeGet; | ||
client._safeSet = client.safeSet; | ||
|
||
client.safeGet = (request, callback) => safeGet.call(client, rs, request, callback); | ||
client.safeSet = (request, callback) => safeSet.call(client, rs, request, callback); | ||
|
||
return client; | ||
} |
Oops, something went wrong.