Stealth -> Stealthkey
More explanatory. Will break up into separate Stealthkey, StealthAddress, and StealthMessage classes.
This commit is contained in:
parent
32fabd0c0f
commit
f028b6b913
|
@ -6,22 +6,22 @@ var Hash = require('../hash');
|
|||
var KDF = require('../kdf');
|
||||
var base58check = require('../base58check');
|
||||
|
||||
var Stealth = function Stealth(obj) {
|
||||
if (!(this instanceof Stealth))
|
||||
return new Stealth(obj);
|
||||
var Stealthkey = function Stealthkey(obj) {
|
||||
if (!(this instanceof Stealthkey))
|
||||
return new Stealthkey(obj);
|
||||
if (obj)
|
||||
this.set(obj);
|
||||
};
|
||||
|
||||
Stealth.prototype.set = function(obj) {
|
||||
Stealthkey.prototype.set = function(obj) {
|
||||
this.payloadKeypair = obj.payloadKeypair || this.payloadKeypair;
|
||||
this.scanKeypair = obj.scanKeypair || this.scanKeypair;
|
||||
return this;
|
||||
};
|
||||
|
||||
Stealth.prototype.fromAddressBuffer = function(buf) {
|
||||
Stealthkey.prototype.fromAddressBuffer = function(buf) {
|
||||
if (!Buffer.isBuffer(buf) || buf.length !== 66)
|
||||
throw new Error('stealth: A stealth address must have length 66');
|
||||
throw new Error('stealthkey: A stealthkey address must have length 66');
|
||||
|
||||
var pPubBuf = buf.slice(0, 33);
|
||||
var sPubBuf = buf.slice(33, 66);
|
||||
|
@ -34,21 +34,21 @@ Stealth.prototype.fromAddressBuffer = function(buf) {
|
|||
return this;
|
||||
};
|
||||
|
||||
Stealth.prototype.fromAddressString = function(str) {
|
||||
Stealthkey.prototype.fromAddressString = function(str) {
|
||||
var buf = base58check.decode(str);
|
||||
this.fromAddressBuffer(buf);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Stealth.prototype.fromRandom = function() {
|
||||
Stealthkey.prototype.fromRandom = function() {
|
||||
this.payloadKeypair = Keypair().fromRandom();
|
||||
this.scanKeypair = Keypair().fromRandom();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Stealth.prototype.getSharedKeypairAsReceiver = function(senderPubkey) {
|
||||
Stealthkey.prototype.getSharedKeypairAsReceiver = function(senderPubkey) {
|
||||
var sharedSecretPoint = senderPubkey.point.mul(this.scanKeypair.privkey.bn);
|
||||
var sharedSecretPubkey = Pubkey({point: sharedSecretPoint});
|
||||
var buf = sharedSecretPubkey.toDER(true);
|
||||
|
@ -57,7 +57,7 @@ Stealth.prototype.getSharedKeypairAsReceiver = function(senderPubkey) {
|
|||
return sharedKeypair;
|
||||
};
|
||||
|
||||
Stealth.prototype.getSharedKeypairAsSender = function(senderKeypair) {
|
||||
Stealthkey.prototype.getSharedKeypairAsSender = function(senderKeypair) {
|
||||
var sharedSecretPoint = this.scanKeypair.pubkey.point.mul(senderKeypair.privkey.bn);
|
||||
var sharedSecretPubkey = Pubkey({point: sharedSecretPoint});
|
||||
var buf = sharedSecretPubkey.toDER(true);
|
||||
|
@ -66,21 +66,21 @@ Stealth.prototype.getSharedKeypairAsSender = function(senderKeypair) {
|
|||
return sharedKeypair;
|
||||
};
|
||||
|
||||
Stealth.prototype.getReceivePubkeyAsReceiver = function(senderPubkey) {
|
||||
Stealthkey.prototype.getReceivePubkeyAsReceiver = function(senderPubkey) {
|
||||
var sharedKeypair = this.getSharedKeypairAsReceiver(senderPubkey);
|
||||
var pubkey = Pubkey({point: this.payloadKeypair.pubkey.point.add(sharedKeypair.pubkey.point)});
|
||||
|
||||
return pubkey;
|
||||
};
|
||||
|
||||
Stealth.prototype.getReceivePubkeyAsSender = function(senderKeypair) {
|
||||
Stealthkey.prototype.getReceivePubkeyAsSender = function(senderKeypair) {
|
||||
var sharedKeypair = this.getSharedKeypairAsSender(senderKeypair);
|
||||
var pubkey = Pubkey({point: this.payloadKeypair.pubkey.point.add(sharedKeypair.pubkey.point)});
|
||||
|
||||
return pubkey;
|
||||
};
|
||||
|
||||
Stealth.prototype.getReceiveKeypair = function(senderPubkey) {
|
||||
Stealthkey.prototype.getReceiveKeypair = function(senderPubkey) {
|
||||
var sharedKeypair = this.getSharedKeypairAsReceiver(senderPubkey);
|
||||
var privkey = Privkey({bn: this.payloadKeypair.privkey.bn.add(sharedKeypair.privkey.bn).mod(Point.getN())});
|
||||
var key = Keypair({privkey: privkey});
|
||||
|
@ -89,7 +89,7 @@ Stealth.prototype.getReceiveKeypair = function(senderPubkey) {
|
|||
return key;
|
||||
};
|
||||
|
||||
Stealth.prototype.isForMe = function(senderPubkey, myPossiblePubkeyhash) {
|
||||
Stealthkey.prototype.isForMe = function(senderPubkey, myPossiblePubkeyhash) {
|
||||
var pubkey = this.getReceivePubkeyAsReceiver(senderPubkey);
|
||||
var pubkeybuf = pubkey.toDER(true);
|
||||
var pubkeyhash = Hash.sha256ripemd160(pubkeybuf);
|
||||
|
@ -100,18 +100,18 @@ Stealth.prototype.isForMe = function(senderPubkey, myPossiblePubkeyhash) {
|
|||
return false;
|
||||
};
|
||||
|
||||
Stealth.prototype.toAddressBuffer = function() {
|
||||
Stealthkey.prototype.toAddressBuffer = function() {
|
||||
var pBuf = this.payloadKeypair.pubkey.toDER(true);
|
||||
var sBuf = this.scanKeypair.pubkey.toDER(true);
|
||||
|
||||
return Buffer.concat([pBuf, sBuf]);
|
||||
};
|
||||
|
||||
Stealth.prototype.toAddressString = function() {
|
||||
Stealthkey.prototype.toAddressString = function() {
|
||||
var buf = this.toAddressBuffer();
|
||||
var b58 = base58check.encode(buf);
|
||||
|
||||
return b58;
|
||||
};
|
||||
|
||||
module.exports = Stealth;
|
||||
module.exports = Stealthkey;
|
190
test/stealth.js
190
test/stealth.js
|
@ -1,190 +0,0 @@
|
|||
var should = require('chai').should();
|
||||
var Stealth = require('../lib/expmt/stealth');
|
||||
var Keypair = require('../lib/keypair');
|
||||
var Privkey = require('../lib/privkey');
|
||||
var Pubkey = require('../lib/pubkey');
|
||||
var BN = require('../lib/bn');
|
||||
var Hash = require('../lib/hash');
|
||||
var base58check = require('../lib/base58check');
|
||||
|
||||
describe('Stealth', function() {
|
||||
|
||||
var stealth = Stealth();
|
||||
stealth.payloadKeypair = Keypair();
|
||||
stealth.payloadKeypair.privkey = Privkey();
|
||||
stealth.payloadKeypair.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 1')));
|
||||
stealth.payloadKeypair.privkey2pubkey();
|
||||
stealth.scanKeypair = Keypair();
|
||||
stealth.scanKeypair.privkey = Privkey();
|
||||
stealth.scanKeypair.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 2')));
|
||||
stealth.scanKeypair.privkey2pubkey();
|
||||
|
||||
var senderKeypair = Keypair();
|
||||
senderKeypair.privkey = Privkey();
|
||||
senderKeypair.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 3')));
|
||||
senderKeypair.privkey2pubkey();
|
||||
|
||||
var addressString = '9dDbC9FzZ74r8njQkXD6W27gtrxLiWaeFPHxeo1fynQRXPicqxVt7u95ozbwoVVMXyrzaHKN9owsteg63FgwDfrxWx82SAW';
|
||||
|
||||
it('should create a new stealth', function() {
|
||||
var stealth = new Stealth();
|
||||
should.exist(stealth);
|
||||
});
|
||||
|
||||
it('should create a new stealth without using "new"', function() {
|
||||
var stealth = Stealth();
|
||||
should.exist(stealth);
|
||||
});
|
||||
|
||||
describe('#set', function() {
|
||||
|
||||
it('should set payload key', function() {
|
||||
should.exist(Stealth().set({payloadKeypair: stealth.payloadKeypair}).payloadKeypair);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#fromAddressBuffer', function() {
|
||||
|
||||
it('should give a stealth address with the right pubkeys', function() {
|
||||
var stealth2 = new Stealth();
|
||||
var buf = base58check.decode(addressString);
|
||||
stealth2.fromAddressBuffer(buf);
|
||||
stealth2.payloadKeypair.pubkey.toString().should.equal(stealth.payloadKeypair.pubkey.toString());
|
||||
stealth2.scanKeypair.pubkey.toString().should.equal(stealth.scanKeypair.pubkey.toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#fromAddressString', function() {
|
||||
|
||||
it('should give a stealth address with the right pubkeys', function() {
|
||||
var stealth2 = new Stealth();
|
||||
stealth2.fromAddressString(addressString);
|
||||
stealth2.payloadKeypair.pubkey.toString().should.equal(stealth.payloadKeypair.pubkey.toString());
|
||||
stealth2.scanKeypair.pubkey.toString().should.equal(stealth.scanKeypair.pubkey.toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#fromRandom', function() {
|
||||
|
||||
it('should create a new stealth from random', function() {
|
||||
var stealth = Stealth().fromRandom();
|
||||
should.exist(stealth.payloadKeypair.privkey.bn.gt(0));
|
||||
should.exist(stealth.scanKeypair.privkey.bn.gt(0));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getSharedKeypairAsReceiver', function() {
|
||||
|
||||
it('should return a key', function() {
|
||||
var key = stealth.getSharedKeypairAsReceiver(senderKeypair.pubkey);
|
||||
(key instanceof Keypair).should.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getSharedKeypairAsSender', function() {
|
||||
|
||||
it('should return a key', function() {
|
||||
var stealth2 = new Stealth();
|
||||
stealth2.payloadKeypair = new Keypair();
|
||||
stealth2.payloadKeypair.pubkey = stealth.payloadKeypair.pubkey;
|
||||
stealth2.scanKeypair = new Keypair();
|
||||
stealth2.scanKeypair.pubkey = stealth.scanKeypair.pubkey;
|
||||
var key = stealth2.getSharedKeypairAsSender(senderKeypair);
|
||||
(key instanceof Keypair).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return the same key as getSharedKeypairAsReceiver', function() {
|
||||
var stealth2 = new Stealth();
|
||||
stealth2.payloadKeypair = new Keypair();
|
||||
stealth2.payloadKeypair.pubkey = stealth.payloadKeypair.pubkey;
|
||||
stealth2.scanKeypair = new Keypair();
|
||||
stealth2.scanKeypair.pubkey = stealth.scanKeypair.pubkey;
|
||||
var key = stealth2.getSharedKeypairAsSender(senderKeypair);
|
||||
|
||||
var key2 = stealth.getSharedKeypairAsReceiver(senderKeypair.pubkey);
|
||||
key.toString().should.equal(key2.toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getReceivePubkeyAsReceiver', function() {
|
||||
|
||||
it('should return a pubkey', function() {
|
||||
var pubkey = stealth.getReceivePubkeyAsReceiver(senderKeypair.pubkey);
|
||||
(pubkey instanceof Pubkey).should.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getReceivePubkeyAsSender', function() {
|
||||
|
||||
it('should return a pubkey', function() {
|
||||
var pubkey = stealth.getReceivePubkeyAsSender(senderKeypair);
|
||||
(pubkey instanceof Pubkey).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return the same pubkey as getReceivePubkeyAsReceiver', function() {
|
||||
var pubkey = stealth.getReceivePubkeyAsSender(senderKeypair);
|
||||
var pubkey2 = stealth.getReceivePubkeyAsReceiver(senderKeypair.pubkey);
|
||||
pubkey2.toString().should.equal(pubkey.toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getReceiveKeypair', function() {
|
||||
|
||||
it('should return a key', function() {
|
||||
var key = stealth.getReceiveKeypair(senderKeypair.pubkey);
|
||||
(key instanceof Keypair).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return a key with the same pubkey as getReceivePubkeyAsReceiver', function() {
|
||||
var key = stealth.getReceiveKeypair(senderKeypair.pubkey);
|
||||
var pubkey = stealth.getReceivePubkeyAsReceiver(senderKeypair.pubkey);
|
||||
key.pubkey.toString().should.equal(pubkey.toString());
|
||||
});
|
||||
|
||||
it('should return private key with length 32 or less', function() {
|
||||
var key = stealth.getReceiveKeypair(senderKeypair.pubkey);
|
||||
key.privkey.bn.toBuffer().length.should.be.below(33);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#isForMe', function() {
|
||||
|
||||
it('should return true if it (the transaction or message) is for me', function() {
|
||||
var pubkeyhash = new Buffer('3cb64fa6ee9b3e8754e3e2bd033bf61048604a99', 'hex');
|
||||
stealth.isForMe(senderKeypair.pubkey, pubkeyhash).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return false if it (the transaction or message) is not for me', function() {
|
||||
var pubkeyhash = new Buffer('00b64fa6ee9b3e8754e3e2bd033bf61048604a99', 'hex');
|
||||
stealth.isForMe(senderKeypair.pubkey, pubkeyhash).should.equal(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#toAddressBuffer', function() {
|
||||
|
||||
it('should return this known address buffer', function() {
|
||||
var buf = stealth.toAddressBuffer();
|
||||
base58check.encode(buf).should.equal(addressString);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#toAddressString', function() {
|
||||
|
||||
it('should return this known address string', function() {
|
||||
stealth.toAddressString().should.equal(addressString);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,190 @@
|
|||
var should = require('chai').should();
|
||||
var Stealthkey = require('../lib/expmt/stealthkey');
|
||||
var Keypair = require('../lib/keypair');
|
||||
var Privkey = require('../lib/privkey');
|
||||
var Pubkey = require('../lib/pubkey');
|
||||
var BN = require('../lib/bn');
|
||||
var Hash = require('../lib/hash');
|
||||
var base58check = require('../lib/base58check');
|
||||
|
||||
describe('Stealthkey', function() {
|
||||
|
||||
var stealthkey = Stealthkey();
|
||||
stealthkey.payloadKeypair = Keypair();
|
||||
stealthkey.payloadKeypair.privkey = Privkey();
|
||||
stealthkey.payloadKeypair.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 1')));
|
||||
stealthkey.payloadKeypair.privkey2pubkey();
|
||||
stealthkey.scanKeypair = Keypair();
|
||||
stealthkey.scanKeypair.privkey = Privkey();
|
||||
stealthkey.scanKeypair.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 2')));
|
||||
stealthkey.scanKeypair.privkey2pubkey();
|
||||
|
||||
var senderKeypair = Keypair();
|
||||
senderKeypair.privkey = Privkey();
|
||||
senderKeypair.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test 3')));
|
||||
senderKeypair.privkey2pubkey();
|
||||
|
||||
var addressString = '9dDbC9FzZ74r8njQkXD6W27gtrxLiWaeFPHxeo1fynQRXPicqxVt7u95ozbwoVVMXyrzaHKN9owsteg63FgwDfrxWx82SAW';
|
||||
|
||||
it('should create a new stealthkey', function() {
|
||||
var stealthkey = new Stealthkey();
|
||||
should.exist(stealthkey);
|
||||
});
|
||||
|
||||
it('should create a new stealthkey without using "new"', function() {
|
||||
var stealthkey = Stealthkey();
|
||||
should.exist(stealthkey);
|
||||
});
|
||||
|
||||
describe('#set', function() {
|
||||
|
||||
it('should set payload key', function() {
|
||||
should.exist(Stealthkey().set({payloadKeypair: stealthkey.payloadKeypair}).payloadKeypair);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#fromAddressBuffer', function() {
|
||||
|
||||
it('should give a stealthkey address with the right pubkeys', function() {
|
||||
var stealthkey2 = new Stealthkey();
|
||||
var buf = base58check.decode(addressString);
|
||||
stealthkey2.fromAddressBuffer(buf);
|
||||
stealthkey2.payloadKeypair.pubkey.toString().should.equal(stealthkey.payloadKeypair.pubkey.toString());
|
||||
stealthkey2.scanKeypair.pubkey.toString().should.equal(stealthkey.scanKeypair.pubkey.toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#fromAddressString', function() {
|
||||
|
||||
it('should give a stealthkey address with the right pubkeys', function() {
|
||||
var stealthkey2 = new Stealthkey();
|
||||
stealthkey2.fromAddressString(addressString);
|
||||
stealthkey2.payloadKeypair.pubkey.toString().should.equal(stealthkey.payloadKeypair.pubkey.toString());
|
||||
stealthkey2.scanKeypair.pubkey.toString().should.equal(stealthkey.scanKeypair.pubkey.toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#fromRandom', function() {
|
||||
|
||||
it('should create a new stealthkey from random', function() {
|
||||
var stealthkey = Stealthkey().fromRandom();
|
||||
should.exist(stealthkey.payloadKeypair.privkey.bn.gt(0));
|
||||
should.exist(stealthkey.scanKeypair.privkey.bn.gt(0));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getSharedKeypairAsReceiver', function() {
|
||||
|
||||
it('should return a key', function() {
|
||||
var key = stealthkey.getSharedKeypairAsReceiver(senderKeypair.pubkey);
|
||||
(key instanceof Keypair).should.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getSharedKeypairAsSender', function() {
|
||||
|
||||
it('should return a key', function() {
|
||||
var stealthkey2 = new Stealthkey();
|
||||
stealthkey2.payloadKeypair = new Keypair();
|
||||
stealthkey2.payloadKeypair.pubkey = stealthkey.payloadKeypair.pubkey;
|
||||
stealthkey2.scanKeypair = new Keypair();
|
||||
stealthkey2.scanKeypair.pubkey = stealthkey.scanKeypair.pubkey;
|
||||
var key = stealthkey2.getSharedKeypairAsSender(senderKeypair);
|
||||
(key instanceof Keypair).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return the same key as getSharedKeypairAsReceiver', function() {
|
||||
var stealthkey2 = new Stealthkey();
|
||||
stealthkey2.payloadKeypair = new Keypair();
|
||||
stealthkey2.payloadKeypair.pubkey = stealthkey.payloadKeypair.pubkey;
|
||||
stealthkey2.scanKeypair = new Keypair();
|
||||
stealthkey2.scanKeypair.pubkey = stealthkey.scanKeypair.pubkey;
|
||||
var key = stealthkey2.getSharedKeypairAsSender(senderKeypair);
|
||||
|
||||
var key2 = stealthkey.getSharedKeypairAsReceiver(senderKeypair.pubkey);
|
||||
key.toString().should.equal(key2.toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getReceivePubkeyAsReceiver', function() {
|
||||
|
||||
it('should return a pubkey', function() {
|
||||
var pubkey = stealthkey.getReceivePubkeyAsReceiver(senderKeypair.pubkey);
|
||||
(pubkey instanceof Pubkey).should.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getReceivePubkeyAsSender', function() {
|
||||
|
||||
it('should return a pubkey', function() {
|
||||
var pubkey = stealthkey.getReceivePubkeyAsSender(senderKeypair);
|
||||
(pubkey instanceof Pubkey).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return the same pubkey as getReceivePubkeyAsReceiver', function() {
|
||||
var pubkey = stealthkey.getReceivePubkeyAsSender(senderKeypair);
|
||||
var pubkey2 = stealthkey.getReceivePubkeyAsReceiver(senderKeypair.pubkey);
|
||||
pubkey2.toString().should.equal(pubkey.toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getReceiveKeypair', function() {
|
||||
|
||||
it('should return a key', function() {
|
||||
var key = stealthkey.getReceiveKeypair(senderKeypair.pubkey);
|
||||
(key instanceof Keypair).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return a key with the same pubkey as getReceivePubkeyAsReceiver', function() {
|
||||
var key = stealthkey.getReceiveKeypair(senderKeypair.pubkey);
|
||||
var pubkey = stealthkey.getReceivePubkeyAsReceiver(senderKeypair.pubkey);
|
||||
key.pubkey.toString().should.equal(pubkey.toString());
|
||||
});
|
||||
|
||||
it('should return private key with length 32 or less', function() {
|
||||
var key = stealthkey.getReceiveKeypair(senderKeypair.pubkey);
|
||||
key.privkey.bn.toBuffer().length.should.be.below(33);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#isForMe', function() {
|
||||
|
||||
it('should return true if it (the transaction or message) is for me', function() {
|
||||
var pubkeyhash = new Buffer('3cb64fa6ee9b3e8754e3e2bd033bf61048604a99', 'hex');
|
||||
stealthkey.isForMe(senderKeypair.pubkey, pubkeyhash).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return false if it (the transaction or message) is not for me', function() {
|
||||
var pubkeyhash = new Buffer('00b64fa6ee9b3e8754e3e2bd033bf61048604a99', 'hex');
|
||||
stealthkey.isForMe(senderKeypair.pubkey, pubkeyhash).should.equal(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#toAddressBuffer', function() {
|
||||
|
||||
it('should return this known address buffer', function() {
|
||||
var buf = stealthkey.toAddressBuffer();
|
||||
base58check.encode(buf).should.equal(addressString);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#toAddressString', function() {
|
||||
|
||||
it('should return this known address string', function() {
|
||||
stealthkey.toAddressString().should.equal(addressString);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue