z-dsa2
Version:
Z-2 Digital Signature Algorithm
363 lines (340 loc) • 10.8 kB
JavaScript
const crypto = require('crypto')
const assert = require('assert')
const tss_1 = require("@stablelib/tss");
module.exports = (options)=>{
options = options|| {};
var CELL_SIZE_L = options.CELL_SIZE_L?parseInt(options.CELL_SIZE_L):32; /// 32 for sha256, 64 for sha512
var CELL_SIZE_S = options.CELL_SIZE_S?parseInt(options.CELL_SIZE_S):16; /// 16 for md5, 32 for sha256, 64 for sha512
var HASH_COUNT = options.HASH_COUNT?parseInt(options.HASH_COUNT):64;/// max 255
var MIN_SHARE_COUNT = options.MIN_SHARE_COUNT?parseInt(options.MIN_SHARE_COUNT):16;
if(isNaN(CELL_SIZE_L)){
throw new Error("CELL_SIZE_L must be an number");
}
if(isNaN(CELL_SIZE_S)){
throw new Error("CELL_SIZE_S must be an number");
}
if(isNaN(HASH_COUNT)){
throw new Error("HASH_COUNT must be an number");
}
if(isNaN(MIN_SHARE_COUNT)){
throw new Error("MIN_SHARE_COUNT must be an number");
}
if(CELL_SIZE_L*2<HASH_COUNT){
throw new Error("HASH_COUNT must be at least CELL_SIZE_L * 2");
}
if(MIN_SHARE_COUNT>CELL_SIZE_L){
throw new Error("MIN_SHARE_COUNT maximum valude should be equal to CELL_SIZE_L:",CELL_SIZE_L);
}
var PRIVATE_KEY_BYTES = CELL_SIZE_L*HASH_COUNT;
var PUBLIC_KEY_BYTES = CELL_SIZE_S*HASH_COUNT;
var SIGNATURE_BYTES = CELL_SIZE_L * CELL_SIZE_L;
var algL;
switch(CELL_SIZE_L){
case 32:
algL = "sha256";
break;
case 64:
algL = "sha512";
break;
default:
throw new Error("Invalid L hash");
}
var algS;
switch(CELL_SIZE_S){
case 16:
algS = "md5";
break;
case 32:
algS = "sha256"
break;
case 64:
algS = "sha512"
break;
default:
throw new Error("Invalid S hash");
}
function eachByte(message,number, iter) {
for(var i = 0; i < message.length; i++) {
if(iter(message[i]%number,i) === false)
return;
}
}
function hashLG (value) {
return crypto.createHash(algL).update(value).digest()
}
function hashSM(value,nonce){
return crypto.createHash(algS).update(value).update(nonce).digest()
}
const keyPairFromPrivateKey = (sk)=>{
if(sk.length != PRIVATE_KEY_BYTES){
throw new Error("Invalid private key length, private key must have "+PRIVATE_KEY_BYTES+" bytes");
}
var PRIVATE = new Buffer(sk);
var shares = [];
for(var i=0;i<MIN_SHARE_COUNT;i++){
var share = new Buffer(CELL_SIZE_L+1);
share[0] = i+1;
PRIVATE.slice(i*CELL_SIZE_L, i*CELL_SIZE_L+CELL_SIZE_L).copy(share,1,0,CELL_SIZE_L);
shares.push(share);
}
var nonce = tss_1.combineRaw(shares);
var PUBLIC = new Buffer(PUBLIC_KEY_BYTES)
for(var i = 0; i < HASH_COUNT; i ++) {
hashSM(hashLG(PRIVATE.slice(i*CELL_SIZE_L, i*CELL_SIZE_L+CELL_SIZE_L)),nonce)
.copy(PUBLIC, i*CELL_SIZE_S, 0, CELL_SIZE_S)
}
return {
private: PRIVATE, public: PUBLIC,nonce:nonce
}
};
const keyPairNew = (nonce)=> {
nonce = nonce||crypto.randomBytes(CELL_SIZE_L);
if(nonce.length != CELL_SIZE_L){
throw new Error("Nonce must be "+(CELL_SIZE_L)+" size in bytes");
}
var shares = tss_1.splitRaw(nonce, MIN_SHARE_COUNT, HASH_COUNT);
var PRIVATE = new Buffer(PRIVATE_KEY_BYTES);
for(var i=0;i<shares.length;i++){
if(shares[i].length != CELL_SIZE_L+1){
throw new Error("Share must be "+(CELL_SIZE_L+1)+" size in bytes");
}
Buffer(shares[i]).copy(PRIVATE,i*CELL_SIZE_L,1,CELL_SIZE_L+1);
}
/// making public key also simular like lamport scheme
var PUBLIC = new Buffer(PUBLIC_KEY_BYTES)
for(var i = 0; i < HASH_COUNT; i ++) {
hashSM(hashLG(PRIVATE.slice(i*CELL_SIZE_L, i*CELL_SIZE_L+CELL_SIZE_L)),nonce)
.copy(PUBLIC, i*CELL_SIZE_S, 0, CELL_SIZE_S)
}
return {
private: PRIVATE, public: PUBLIC, nonce:nonce
}
};
const sign = (PRIVATE, message)=> {
if(message.length > CELL_SIZE_L)
throw new Error('message has incorrect size, it can have up to '+CELL_SIZE_L+" bytes")
if(PRIVATE.length != PRIVATE_KEY_BYTES){
throw new Error("Invalid private key length, private key must have "+PRIVATE_KEY_BYTES+" bytes");
}
var m = hashLG(message)
var sig = [];
var used = [];
eachByte(m,HASH_COUNT, function (bit, i) {
if(used.length>= MIN_SHARE_COUNT){
sig.push(hashLG(PRIVATE.slice(CELL_SIZE_L*bit, CELL_SIZE_L*bit+CELL_SIZE_L)));
}else{
if(used.indexOf(bit) == -1){
used.push(bit);
}else {
for(var c=0;c<HASH_COUNT;c++){
if(used.indexOf(c) == -1){
used.push(c);
bit = c;
break;
}
}
}
sig.push(PRIVATE.slice(CELL_SIZE_L*bit, CELL_SIZE_L*bit+CELL_SIZE_L));
}
})
return Buffer.concat(sig)
};
const verify = (PUBLIC, message, SIGNATURE)=>{
if(message.length > CELL_SIZE_L)
throw new Error('Message has incorrect size, it can have up to '+CELL_SIZE_L+" bytes");
if(PUBLIC.length != PUBLIC_KEY_BYTES){
throw new Error("Invalid public key size, public key must have "+PUBLIC_KEY_BYTES+" bytes");
}
if(SIGNATURE.length != SIGNATURE_BYTES){
throw new Error("Invalid signature size, signature must have "+SIGNATURE_BYTES+" bytes");
}
var sig = []
var m = hashLG(message)
var shares = [];
try {
var used = [];
var nonce;
eachByte(m,HASH_COUNT, function (bit, i) {
if(used.indexOf(bit) == -1){
used.push(bit);
}else {
for(var c=0;c<HASH_COUNT;c++){
if(used.indexOf(c) == -1){
used.push(c);
bit = c;
break;
}
}
}
var share = new Buffer(CELL_SIZE_L+1);
share[0] = bit+1;
SIGNATURE.copy(share,1,i*CELL_SIZE_L, (i+1)*CELL_SIZE_L);
shares.push(share);
if(shares.length>=MIN_SHARE_COUNT)
return false;
});
if(shares.length<MIN_SHARE_COUNT){
// console.log("warning min share",shares.length);
return 0;
}
/// get nonce for future verification.
try{
nonce = tss_1.combineRaw(shares);
}catch(e){
console.log(e);
return 0;
}
/// Similar to lamport verification. lamport uses bits, we use bytes,
/// we added extra nonce via Shamir's secret share, that enable use of much smallar
/// signatures and keys in comparing to Lamport signature scheme
/// Lamport expose 50% of private key in first signature, we can expose minimal, depends of configuration
/// WARNING: same key should not be used more the once.
used = [];
var value = 0;
eachByte(m,HASH_COUNT, function (bit, i) {
if(used.length>= MIN_SHARE_COUNT){
assert.deepEqual(
hashSM(SIGNATURE.slice(i*CELL_SIZE_L, (i+1)*CELL_SIZE_L),nonce),
PUBLIC.slice(CELL_SIZE_S*bit, CELL_SIZE_S*bit+CELL_SIZE_S),
'not authentic'
)
}else {
if(used.indexOf(bit) == -1){
used.push(bit);
}else {
for(var c=0;c<HASH_COUNT;c++){
if(used.indexOf(c) == -1){
used.push(c);
bit = c;
break;
}
}
}
value+= bit;
assert.deepEqual(
hashSM(hashLG(SIGNATURE.slice(i*CELL_SIZE_L, (i+1)*CELL_SIZE_L)),nonce),
PUBLIC.slice(CELL_SIZE_S*bit, CELL_SIZE_S*bit+CELL_SIZE_S),
'not authentic'
)
}
})
//console.log(used);
return value;
} catch (err) {
if(/not authentic/.test(err.message)) return 0;
throw err
}
};
const verifyDebug = (PUBLIC, message, SIGNATURE,usedIndex,plainUsed)=>{
if(message.length > CELL_SIZE_L)
throw new Error('Message has incorrect size, it can have up to '+CELL_SIZE_L+" bytes");
if(PUBLIC.length != PUBLIC_KEY_BYTES){
throw new Error("Invalid public key size, public key must have "+PUBLIC_KEY_BYTES+" bytes");
}
if(SIGNATURE.length != SIGNATURE_BYTES){
throw new Error("Invalid signature size, signature must have "+SIGNATURE_BYTES+" bytes");
}
if(usedIndex&&!(usedIndex instanceof Array)){
throw new Error("usedIndex must be an array");
}
if(plainUsed&&!(plainUsed instanceof Array)){
throw new Error("plainUsed must be an array");
}
var sig = []
var m = hashLG(message)
var shares = [];
try {
var used = [];
var nonce;
eachByte(m,HASH_COUNT, function (bit, i) {
if(used.indexOf(bit) == -1){
used.push(bit);
}else {
for(var c=0;c<HASH_COUNT;c++){
if(used.indexOf(c) == -1){
used.push(c);
bit = c;
break;
}
}
}
var share = new Buffer(CELL_SIZE_L+1);
share[0] = bit+1;
SIGNATURE.copy(share,1,i*CELL_SIZE_L, (i+1)*CELL_SIZE_L);
shares.push(share);
if(shares.length>=MIN_SHARE_COUNT)
return false;
});
if(shares.length<MIN_SHARE_COUNT){
// console.log("warning min share",shares.length);
return 0;
}
/// get nonce for future verification.
try{
nonce = tss_1.combineRaw(shares);
}catch(e){
console.log(e);
return 0;
}
/// Similar to lamport verification. lamport uses bits, we use bytes,
/// we added extra nonce via Shamir's secret share, that enable use of much smallar
/// signatures and keys in comparing to Lamport signature scheme
/// Lamport expose 50% of private key in first signature, we can expose minimal, depends of configuration
/// WARNING: same key should not be used more the once.
used = [];
var value = 0;
eachByte(m,HASH_COUNT, function (bit, i) {
if(used.length>= MIN_SHARE_COUNT){
if(usedIndex&&usedIndex.indexOf(bit) == -1)
usedIndex.push(bit);
assert.deepEqual(
hashSM(SIGNATURE.slice(i*CELL_SIZE_L, (i+1)*CELL_SIZE_L),nonce),
PUBLIC.slice(CELL_SIZE_S*bit, CELL_SIZE_S*bit+CELL_SIZE_S),
'not authentic'
)
}else {
if(used.indexOf(bit) == -1){
used.push(bit);
}else {
for(var c=0;c<HASH_COUNT;c++){
if(used.indexOf(c) == -1){
used.push(c);
bit = c;
break;
}
}
}
if(usedIndex&&usedIndex.indexOf(bit) == -1)
usedIndex.push(bit);
if(plainUsed&&plainUsed.indexOf(bit) == -1)
plainUsed.push(bit);
value+= bit;
assert.deepEqual(
hashSM(hashLG(SIGNATURE.slice(i*CELL_SIZE_L, (i+1)*CELL_SIZE_L)),nonce),
PUBLIC.slice(CELL_SIZE_S*bit, CELL_SIZE_S*bit+CELL_SIZE_S),
'not authentic'
)
}
})
//console.log(used);
return value;
} catch (err) {
if(/not authentic/.test(err.message)) return 0;
throw err
}
};
return {
CELL_SIZE_L:CELL_SIZE_L,
CELL_SIZE_S:CELL_SIZE_S,
HASH_COUNT:HASH_COUNT,
MIN_SHARE_COUNT:MIN_SHARE_COUNT,
PRIVATE_KEY_BYTES:PRIVATE_KEY_BYTES,
PUBLIC_KEY_BYTES:PUBLIC_KEY_BYTES,
SIGNATURE_BYTES:SIGNATURE_BYTES,
keyPairNew:keyPairNew,
keyPairFromPrivateKey:keyPairFromPrivateKey,
sign:sign,
verify:verify,
verifyDebug:verifyDebug
};
}