Compare commits
2 Commits
master
...
ref-create
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5e2aa3024 | ||
|
|
bcfa524270 |
@ -2,6 +2,8 @@
|
||||
|
||||
* `randomBytes` is removed and will throw an exception. Bring your own CSPRNG for your runtime
|
||||
* `randomfill`
|
||||
* `createSign()` & `createVerify()` are carved out (could not get noble to work with it)
|
||||
* for `createECDH`, curves `secp224r1` & `prime192v1` are carved out (not supported in noble)
|
||||
|
||||
# crypto-browserify <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
||||
|
||||
|
||||
19
index.js
19
index.js
@ -50,12 +50,21 @@ exports.getDiffieHellman = dh.getDiffieHellman;
|
||||
exports.createDiffieHellman = dh.createDiffieHellman;
|
||||
exports.DiffieHellman = dh.DiffieHellman;
|
||||
|
||||
var sign = require('./noble-sign-wrapper');
|
||||
exports.createSign = function () {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
exports.createSign = sign.createSign;
|
||||
exports.Sign = sign.Sign;
|
||||
exports.createVerify = sign.createVerify;
|
||||
exports.Verify = sign.Verify;
|
||||
exports.Sign = function () {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
exports.createVerify = function () {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
exports.Verify = function () {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
exports.createECDH = require('./noble-ecdh-wrapper');
|
||||
|
||||
|
||||
@ -4,23 +4,16 @@ var secp256k1 = require('@noble/curves/secp256k1');
|
||||
var nist = require('@noble/curves/nist');
|
||||
var Buffer = require('safe-buffer').Buffer;
|
||||
|
||||
// Fallback to create-ecdh/browser for unsupported curves and hybrid format
|
||||
var createEcdhBrowser = require('create-ecdh/browser');
|
||||
|
||||
// Curve name mapping
|
||||
var curveMap = {
|
||||
secp256k1: secp256k1.secp256k1,
|
||||
secp224r1: null, // Will fallback to create-ecdh
|
||||
secp224r1: null, // Not available in noble
|
||||
prime256v1: nist.p256,
|
||||
prime192v1: null // Not available in noble
|
||||
};
|
||||
|
||||
function ECDH(curveName) {
|
||||
// Fallback for secp224r1 and prime192v1
|
||||
if (curveName === 'secp224r1' || curveName === 'prime192v1') {
|
||||
// Use create-ecdh/browser fallback for secp224r1 and prime192v1
|
||||
return createEcdhBrowser(curveName);
|
||||
}
|
||||
|
||||
if (!curveMap[curveName]) {
|
||||
throw new Error('Unsupported curve: ' + curveName);
|
||||
}
|
||||
@ -51,11 +44,9 @@ ECDH.prototype.getPublicKey = function (encoding, format) {
|
||||
throw new Error('Public key not set');
|
||||
}
|
||||
|
||||
// Fallback for hybrid format
|
||||
if (format === 'hybrid') {
|
||||
// Use create-ecdh/browser fallback for hybrid format
|
||||
// This is required because noble-curves does not support hybrid format
|
||||
return createEcdhBrowser(this.curveName).setPrivateKey(this.getPrivateKey()).getPublicKey(encoding, format);
|
||||
// noble-curves does not support hybrid format
|
||||
throw new Error('Unsupported format: ' + format);
|
||||
}
|
||||
|
||||
var key;
|
||||
|
||||
@ -1,358 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* global Uint8Array */
|
||||
|
||||
var secp256k1 = require('@noble/curves/secp256k1');
|
||||
var p256 = require('@noble/curves/p256');
|
||||
var p384 = require('@noble/curves/p384');
|
||||
var p521 = require('@noble/curves/p521');
|
||||
var ed25519 = require('@noble/curves/ed25519');
|
||||
var ed448 = require('@noble/curves/ed448');
|
||||
var sha2 = require('@noble/hashes/sha2');
|
||||
var legacy = require('@noble/hashes/legacy');
|
||||
|
||||
// For RSA algorithms, fall back to browserify-sign (legacy, not noble)
|
||||
var browserifySign = require('browserify-sign');
|
||||
|
||||
// Map algorithm names to noble curve functions
|
||||
var curveFunctions = {
|
||||
secp256k1: secp256k1,
|
||||
p256: p256,
|
||||
p384: p384,
|
||||
p521: p521,
|
||||
ed25519: ed25519,
|
||||
ed448: ed448
|
||||
};
|
||||
|
||||
// Map hash algorithms to noble hash functions
|
||||
var hashFunctions = {
|
||||
sha1: legacy.sha1,
|
||||
sha224: sha2.sha224,
|
||||
sha256: sha2.sha256,
|
||||
sha384: sha2.sha384,
|
||||
sha512: sha2.sha512,
|
||||
md5: legacy.md5,
|
||||
rmd160: legacy.ripemd160
|
||||
};
|
||||
|
||||
// Helper function to convert data to Uint8Array
|
||||
function toUint8Array(data) {
|
||||
if (data instanceof Uint8Array) {
|
||||
return data;
|
||||
}
|
||||
if (typeof data === 'string') {
|
||||
return new Uint8Array(Buffer.from(data, 'utf8'));
|
||||
}
|
||||
if (Buffer.isBuffer(data)) {
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
throw new Error('Unsupported data type');
|
||||
}
|
||||
|
||||
// Helper function to create browserify-sign fallback
|
||||
function createSignFallback(algorithm) {
|
||||
var signInstance = browserifySign.createSign(algorithm);
|
||||
return {
|
||||
update: signInstance.update.bind(signInstance),
|
||||
sign: signInstance.sign.bind(signInstance)
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to create browserify-sign verify fallback
|
||||
function createVerifyFallback(algorithm) {
|
||||
var verifyInstance = browserifySign.createVerify(algorithm);
|
||||
return {
|
||||
update: verifyInstance.update.bind(verifyInstance),
|
||||
verify: verifyInstance.verify.bind(verifyInstance)
|
||||
};
|
||||
}
|
||||
|
||||
function parseSingleHash(parts) {
|
||||
var singleHashName = parts[0];
|
||||
var singleHash = hashFunctions[singleHashName];
|
||||
if (singleHash) {
|
||||
return {
|
||||
useNoble: true,
|
||||
curve: curveFunctions.p256,
|
||||
hash: singleHash,
|
||||
reason: 'legacy-hash-only'
|
||||
};
|
||||
}
|
||||
return {
|
||||
useNoble: false,
|
||||
reason: 'invalid-format'
|
||||
};
|
||||
}
|
||||
|
||||
function parseMultiPart(parts) {
|
||||
var curveName = parts[0];
|
||||
var hashName = parts[1];
|
||||
if (curveName === 'rsa') {
|
||||
return {
|
||||
useNoble: false,
|
||||
reason: 'rsa'
|
||||
};
|
||||
}
|
||||
var curve = curveFunctions[curveName];
|
||||
var hash = hashFunctions[hashName];
|
||||
if (!curve) {
|
||||
return {
|
||||
useNoble: false,
|
||||
reason: 'unsupported-curve'
|
||||
};
|
||||
}
|
||||
if (!hash) {
|
||||
return {
|
||||
useNoble: false,
|
||||
reason: 'unsupported-hash'
|
||||
};
|
||||
}
|
||||
return {
|
||||
useNoble: true,
|
||||
curve: curve,
|
||||
hash: hash
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to parse algorithm and determine if it should use noble
|
||||
function parseAlgorithm(algorithm) {
|
||||
var parts = algorithm.toLowerCase().split('-');
|
||||
if (parts.length === 1) {
|
||||
return parseSingleHash(parts);
|
||||
}
|
||||
if (parts.length < 2) {
|
||||
return { useNoble: false, reason: 'invalid-format' };
|
||||
}
|
||||
return parseMultiPart(parts);
|
||||
}
|
||||
|
||||
// Helper function to try noble curve signing
|
||||
function tryNobleSign(curve, hashedData, privateKeyBytes) {
|
||||
if (curve.sign) {
|
||||
return curve.sign(hashedData, privateKeyBytes);
|
||||
}
|
||||
if (curve.Signature && curve.Signature.sign) {
|
||||
return curve.Signature.sign(hashedData, privateKeyBytes);
|
||||
}
|
||||
if (curve.secp256k1 && curve.secp256k1.sign) {
|
||||
return curve.secp256k1.sign(hashedData, privateKeyBytes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper function to try noble curve verification
|
||||
function tryNobleVerify(curve, signatureBytes, hashedData, publicKeyBytes) {
|
||||
if (curve.verify) {
|
||||
return curve.verify(signatureBytes, hashedData, publicKeyBytes);
|
||||
}
|
||||
if (curve.Signature && curve.Signature.verify) {
|
||||
return curve.Signature.verify(signatureBytes, hashedData, publicKeyBytes);
|
||||
}
|
||||
if (curve.secp256k1 && curve.secp256k1.verify) {
|
||||
return curve.secp256k1.verify(signatureBytes, hashedData, publicKeyBytes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper function to create fallback instances
|
||||
function createFallbackInst(algorithm, data, type) {
|
||||
var inst = type === 'sign' ? createSignFallback(algorithm) : createVerifyFallback(algorithm);
|
||||
inst.data = data;
|
||||
return inst;
|
||||
}
|
||||
|
||||
function Sign(algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
this.curve = null;
|
||||
this.hashFunction = null;
|
||||
this.privateKey = null;
|
||||
this.data = [];
|
||||
this.finalized = false;
|
||||
|
||||
var parsed = parseAlgorithm(algorithm);
|
||||
if (!parsed.useNoble) {
|
||||
var fallback = createSignFallback(algorithm);
|
||||
this.update = fallback.update;
|
||||
this.sign = fallback.sign;
|
||||
return;
|
||||
}
|
||||
|
||||
this.curve = parsed.curve;
|
||||
this.hashFunction = parsed.hash;
|
||||
}
|
||||
|
||||
Sign.prototype.update = function (data, encoding) {
|
||||
if (this.finalized) {
|
||||
throw new Error('Sign already finalized');
|
||||
}
|
||||
var uint8Data;
|
||||
if (encoding) {
|
||||
if (encoding === 'hex') {
|
||||
uint8Data = new Uint8Array(Buffer.from(data, 'hex'));
|
||||
} else if (encoding === 'base64') {
|
||||
uint8Data = new Uint8Array(Buffer.from(data, 'base64'));
|
||||
} else {
|
||||
throw new Error('Unsupported encoding: ' + encoding);
|
||||
}
|
||||
} else {
|
||||
uint8Data = toUint8Array(data);
|
||||
}
|
||||
this.data.push(uint8Data);
|
||||
return this;
|
||||
};
|
||||
|
||||
Sign.prototype.sign = function (privateKey, encoding) {
|
||||
if (this.finalized) {
|
||||
throw new Error('Sign already finalized');
|
||||
}
|
||||
if (!privateKey) {
|
||||
throw new Error('Private key is required');
|
||||
}
|
||||
|
||||
// Concatenate all data
|
||||
var allData = new Uint8Array(this.data.reduce(function (total, chunk) {
|
||||
return total + chunk.length;
|
||||
}, 0));
|
||||
var offset = 0;
|
||||
for (var i = 0; i < this.data.length; i++) {
|
||||
allData.set(this.data[i], offset);
|
||||
offset += this.data[i].length;
|
||||
}
|
||||
|
||||
// Hash the data
|
||||
var hashedData = this.hashFunction(allData);
|
||||
|
||||
// Convert private key to Uint8Array if needed
|
||||
var privateKeyBytes = toUint8Array(privateKey);
|
||||
|
||||
// Try to sign using noble curve
|
||||
var signature;
|
||||
try {
|
||||
signature = tryNobleSign(this.curve, hashedData, privateKeyBytes);
|
||||
if (signature === null) {
|
||||
var signFallback = createFallbackInst(this.algorithm, this.data, 'sign');
|
||||
return signFallback.sign(privateKey, encoding);
|
||||
}
|
||||
} catch (error) {
|
||||
var signFallbackCatch = createFallbackInst(this.algorithm, this.data, 'sign');
|
||||
return signFallbackCatch.sign(privateKey, encoding);
|
||||
}
|
||||
|
||||
this.finalized = true;
|
||||
|
||||
// Return signature based on encoding
|
||||
if (encoding === 'hex') {
|
||||
return signature.toHex ? signature.toHex() : Buffer.from(signature).toString('hex');
|
||||
}
|
||||
if (encoding === 'base64') {
|
||||
return signature.toBase64 ? signature.toBase64() : Buffer.from(signature).toString('base64');
|
||||
}
|
||||
return signature.toBytes ? signature.toBytes() : Buffer.from(signature);
|
||||
};
|
||||
|
||||
function Verify(algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
this.curve = null;
|
||||
this.hashFunction = null;
|
||||
this.publicKey = null;
|
||||
this.data = [];
|
||||
this.finalized = false;
|
||||
|
||||
var parsed = parseAlgorithm(algorithm);
|
||||
if (!parsed.useNoble) {
|
||||
var fallback = createVerifyFallback(algorithm);
|
||||
this.update = fallback.update;
|
||||
this.verify = fallback.verify;
|
||||
return;
|
||||
}
|
||||
|
||||
this.curve = parsed.curve;
|
||||
this.hashFunction = parsed.hash;
|
||||
}
|
||||
|
||||
Verify.prototype.update = function (data, encoding) {
|
||||
if (this.finalized) {
|
||||
throw new Error('Verify already finalized');
|
||||
}
|
||||
var uint8Data;
|
||||
if (encoding) {
|
||||
if (encoding === 'hex') {
|
||||
uint8Data = new Uint8Array(Buffer.from(data, 'hex'));
|
||||
} else if (encoding === 'base64') {
|
||||
uint8Data = new Uint8Array(Buffer.from(data, 'base64'));
|
||||
} else {
|
||||
throw new Error('Unsupported encoding: ' + encoding);
|
||||
}
|
||||
} else {
|
||||
uint8Data = toUint8Array(data);
|
||||
}
|
||||
this.data.push(uint8Data);
|
||||
return this;
|
||||
};
|
||||
|
||||
Verify.prototype.verify = function (publicKey, signature, encoding) {
|
||||
if (this.finalized) {
|
||||
throw new Error('Verify already finalized');
|
||||
}
|
||||
if (!publicKey) {
|
||||
throw new Error('Public key is required');
|
||||
}
|
||||
if (!signature) {
|
||||
throw new Error('Signature is required');
|
||||
}
|
||||
|
||||
// Concatenate all data
|
||||
var allData = new Uint8Array(this.data.reduce(function (total, chunk) {
|
||||
return total + chunk.length;
|
||||
}, 0));
|
||||
var offset = 0;
|
||||
for (var i = 0; i < this.data.length; i++) {
|
||||
allData.set(this.data[i], offset);
|
||||
offset += this.data[i].length;
|
||||
}
|
||||
|
||||
// Hash the data
|
||||
var hashedData = this.hashFunction(allData);
|
||||
|
||||
// Parse signature based on encoding
|
||||
var signatureBytes;
|
||||
if (encoding === 'hex') {
|
||||
signatureBytes = new Uint8Array(Buffer.from(signature, 'hex'));
|
||||
} else if (encoding === 'base64') {
|
||||
signatureBytes = new Uint8Array(Buffer.from(signature, 'base64'));
|
||||
} else {
|
||||
signatureBytes = toUint8Array(signature);
|
||||
}
|
||||
|
||||
// Convert public key to Uint8Array if needed
|
||||
var publicKeyBytes = toUint8Array(publicKey);
|
||||
|
||||
// Verify the signature using noble curve
|
||||
var isValid;
|
||||
try {
|
||||
isValid = tryNobleVerify(this.curve, signatureBytes, hashedData, publicKeyBytes);
|
||||
if (isValid === null) {
|
||||
var verifyFallback = createFallbackInst(this.algorithm, this.data, 'verify');
|
||||
return verifyFallback.verify(publicKey, signature, encoding);
|
||||
}
|
||||
} catch (error) {
|
||||
var verifyFallbackCatch = createFallbackInst(this.algorithm, this.data, 'verify');
|
||||
return verifyFallbackCatch.verify(publicKey, signature, encoding);
|
||||
}
|
||||
|
||||
this.finalized = true;
|
||||
return isValid;
|
||||
};
|
||||
|
||||
// Export the functions
|
||||
exports.createSign = function (algorithm) {
|
||||
return new Sign(algorithm);
|
||||
};
|
||||
|
||||
exports.Sign = Sign;
|
||||
|
||||
exports.createVerify = function (algorithm) {
|
||||
return new Verify(algorithm);
|
||||
};
|
||||
|
||||
exports.Verify = Verify;
|
||||
@ -23,11 +23,8 @@
|
||||
"@noble/ciphers": "^1.3.0",
|
||||
"@noble/curves": "^1.9.2",
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"browserify-sign": "^4.2.3",
|
||||
"create-ecdh": "^4.0.4",
|
||||
"diffie-hellman": "^5.0.3",
|
||||
"public-encrypt": "^4.0.3",
|
||||
"randomfill": "^1.0.4"
|
||||
"public-encrypt": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ljharb/eslint-config": "^21.1.1",
|
||||
|
||||
11
test/ecdh.js
11
test/ecdh.js
@ -2,9 +2,9 @@
|
||||
|
||||
var mods = [
|
||||
'secp256k1',
|
||||
'secp224r1',
|
||||
'prime256v1',
|
||||
'prime192v1'
|
||||
// 'secp224r1', // not supported in noble
|
||||
'prime256v1'
|
||||
// 'prime192v1' // not supported in noble
|
||||
];
|
||||
var test = require('tape');
|
||||
var createECDH1 = require('../noble-ecdh-wrapper');
|
||||
@ -40,7 +40,7 @@ mods.forEach(function (mod) {
|
||||
});
|
||||
|
||||
test('createECDH: ' + mod + ' set stuff', function (t) {
|
||||
t.plan(5);
|
||||
t.plan(4);
|
||||
var dh1 = createECDH1(mod);
|
||||
var dh2 = createECDH2(mod);
|
||||
dh1.generateKeys();
|
||||
@ -54,7 +54,8 @@ mods.forEach(function (mod) {
|
||||
var pubk2 = dh2.getPublicKey();
|
||||
t.equals(pubk1.toString('hex'), pubk2.toString('hex'), 'same public keys, uncompressed');
|
||||
t.equals(dh1.getPublicKey('hex', 'compressed'), dh2.getPublicKey('hex', 'compressed'), 'same public keys compressed');
|
||||
t.equals(dh1.getPublicKey('hex', 'hybrid'), dh2.getPublicKey('hex', 'hybrid'), 'same public keys hybrid');
|
||||
// not supported in noble
|
||||
// t.equals(dh1.getPublicKey('hex', 'hybrid'), dh2.getPublicKey('hex', 'hybrid'), 'same public keys hybrid');
|
||||
var pub1 = dh1.computeSecret(pubk2).toString('hex');
|
||||
var pub2 = dh2.computeSecret(pubk1).toString('hex');
|
||||
t.equals(pub1, pub2, 'equal secrets');
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var nodeCrypto = require('../');
|
||||
var ourCrypto = require('../noble-sign-wrapper');
|
||||
var nodeCrypto = require('crypto');
|
||||
var ourCrypto = require('../');
|
||||
|
||||
var rsa = {
|
||||
'private': '2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d4949456a77494241414b422f6779376d6a615767506546645659445a5752434139424e69763370506230657332372b464b593068737a4c614f7734374578430a744157704473483438545841667948425977424c67756179666b344c4749757078622b43474d62526f337845703043626659314a62793236543976476a5243310a666f484444554a4738347561526279487161663469367a74346756522b786c4145496a6b614641414b38634f6f58415431435671474c4c6c6a554363684c38500a6a61486a2f7972695a2f53377264776c49334c6e41427877776d4c726d522f7637315774706d4f2f614e47384e2b31706f2b5177616768546b79513539452f5a0a7641754f6b4657486f6b32712f523650594161326a645a397a696d3046714f502b6e6b5161454452624246426d4271547635664647666b32577341664b662f520a47302f5646642b5a654d353235315465547658483639356e6c53476175566c3941674d42414145436766344c725748592f6c35346f7554685a577676627275670a70667a36734a583267396c3779586d576c455773504543566f2f375355627059467074364f5a7939397a53672b494b624771574b6664686f4b725477495674430a4c30595a304e6c6d646e414e53497a30726f785147375a786b4c352b764853772f506d443978345577662b437a38684154436d4e42763171633630646b7975570a34434c71653732716154695657526f4f316961675167684e634c6f6f36765379363545784c614344545068613779753276773468465a705769456a57346478660a7246644c696978353242433836596c416c784d452f724c6738494a5676696c62796f39615764586d784f6155544c527636506b4644312f6756647738563951720a534c4e39466c4b326b6b6a695830647a6f6962765a7733744d6e74337979644178305838372b734d5256616843316270336b56507a3448793045575834514a2f0a504d33317647697549546b324e43643531445874314c746e324f503546614a536d4361456a6830586b5534716f7559796a585774384275364254436c327675610a466730556a6939432b496b504c6d61554d624d494f7761546b386357714c74685378734c6537304a354f6b477267664b554d2f772b4248483150742f506a7a6a0a432b2b6c306b6946614f5644566141563947704c504c43426f4b2f50433952622f72784d4d6f43434e774a2f4e5a756564496e793277334c4d69693737682f540a7a53766572674e47686a5936526e7661386c4c584a36646c726b6350417970733367577778716a344e5230542b474d3062445550564c62374d303758563753580a7637564a476d35324a625247774d3173732b72385854544e656d65476b2b5752784737546774734d715947584c66423851786b2f66352f4d63633030546c38750a7758464e7366784a786d7436416273547233673336774a2f49684f6e69627a3941642b6e63686c426e4e3351655733434b48717a61523138766f717674566d320a6b4a66484b31357072482f7353476d786d6945476772434a545a78744462614e434f372f56426a6e4b756455554968434177734c747571302f7a7562397641640a384731736366497076357161534e7a6d4b6f5838624f77417276725336775037794b726354737557496c484438724a5649374945446e516f5470354738664b310a68774a2f4d4968384d35763072356455594576366f494a5747636c65364148314a6d73503557496166677137325a32323838704863434648774e59384467394a0a3736517377564c6e556850546c6d6d33454f4f50474574616d32694144357230416679746c62346c624e6f51736a32737a65584f4e4458422b366f7565616a680a564e454c55723848635350356c677a525a6a4a57366146497a6a394c44526d516e55414f6a475358564f517445774a2f4d43515a374e2f763464494b654452410a3864385545785a332b674748756d7a697a7447524a30745172795a483250616b50354937562b316c377145556e4a3263336d462b65317634314570394c4376680a627a72504b773964786831386734622b37624d707357506e7372614b6836697078633761614f615a5630447867657a347a635a753050316f6c4f30634e334b4d0a6e784a305064733352386241684e43446453324a5a61527035513d3d0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a',
|
||||
@ -22,7 +22,7 @@ ec['public'] = new Buffer(ec['public'], 'hex');
|
||||
function testit(keys, message, scheme) {
|
||||
var pub = keys['public'];
|
||||
var priv = keys['private'];
|
||||
test(message.toString(), function (t) {
|
||||
test(message.toString(), { skip: true }, function (t) {
|
||||
t.test('js sign and verify', function (st) {
|
||||
st.plan(1);
|
||||
var mySign = ourCrypto.createSign(scheme);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user