-
Notifications
You must be signed in to change notification settings - Fork 1.9k
SC1014
# WRONG
if [ grep -q pattern file ]
then
echo "Found a match"
fi# WRONG
if ! [[ logname == $LOGNAME ]]
then
echo "Possible `su` shell"
fiif grep -q pattern file
then
echo "Found a match"
fiif ! [[ $(logname) == $LOGNAME ]]
then
echo "Possible `su` shell"
fi[ ... ] as shell syntax is a simple command that tests for whether certain conditions are true or false, such as whether the value assigned to a variable has a non-zero length ([ -n "${foo}" ]) or whether a file system object is a directory ([ -d "${dir}" ]). If-then-(elif-then)-else-fi statements are logical constructs which themselves contain lists of commands which can include simple commands.
[ is just regular command, like whoami or grep, but with a funny name (see ls -l /bin/[). It's a shorthand for test. [[ is similar to both [ and test, but [[ offers some additional unary operators, such as '=~' the regular expression comparison operator. It allows one to use extglobs such as @(foo|bar) (a "bashism"), among some other less commonly used features.
[[, [ and test are often used within if...fi constructs in the conditional commands position: which is between the 'if' and the 'then.'
There are certain shell syntaxes which can be wrapped directly around simple commands, in particular:
- (1)
{ ...;}, group commands, - (2)
$( ... ), command substitutions, - (3)
<( ... )and>( ... ), process substitutions, - (4)
( ... ), subshells, and - (5)
$(( ... ))and(( ... )), arithmetic evaluations.
Some examples include:
- (1)
{ echo {a..z}; echo {0..9};} > ~/f, - (2)
[[ $(logname) == $LOGNAME ]], - (3)
readarray -t files < <( find ...), - (4)
(cd /foo || exit 1; tar ...), and - (5)
dd bs=$((2**12)) count=1 if=/dev/zero of=/tmp/zeroed-block, respectively.
Note how in example (2) logname is enclosed directly within a command substitution, which is itself enclosed within a [[ reserved word / conditional expression / compound command.
If you want to check the exit status of a certain command, use that command directly as demonstrated in the correct code, above.
If you want to check the output of a command, use "$(..)" to get its output, and then use test/[ or [[ to do a string comparison:
# Check output of `whoami` against the string `root`
if [ "$(whoami)" = "root" ]
then
echo "Running as root"
fiNone.
For more information, see this problem in the Bash Pitfall list, or generally Tests and Conditionals in the wooledge.org BashGuide
#!/usr/bin/env node “use strict”;
const crypto = require(“crypto”);
/* ================= Hash / Encoding ================= */
function sha256(b){ return crypto.createHash(“sha256”).update(b).digest(); } function dsha256(b){ return sha256(sha256(b)); } function hex(b){ return Buffer.from(b).toString(“hex”); }
function b64u(b){
return Buffer.from(b).toString("base64")
.replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");
}
function b64uDec(s){
const p=s.replace(/-/g,"+").replace(/_/g,"/")+"===".slice((s.length+3)%4); return Buffer.from(p,"base64");
}
/* ================= STDIN ================= */
async function readStdin(){
return new Promise(res=>{
const chunks=[];
process.stdin.on("data",c=>chunks.push(c));
process.stdin.on("end",()=>res(Buffer.concat(chunks)));
if(process.stdin.isTTY) res(Buffer.alloc(0));
});
}
/* ================= Ints ================= */
function u32le(n){ const b=Buffer.alloc(4); b.writeUInt32LE(n>>>0,0); return b; } function u32be(n){ const b=Buffer.alloc(4); b.writeUInt32BE(n>>>0,0); return b; }
function u64le(n){
const b=Buffer.alloc(8);
let x=BigInt(n);
for(let i=0;i<8;i++){ b[i]=Number(x&0xffn); x>>=8n; }
return b;
}
function varint(n){
if(n<0xfd) return Buffer.from([n]); const b=Buffer.alloc(3); b[0]=0xfd; b.writeUInt16LE(n,1); return b;
}
/* ================= PoW ================= */
function bitsToTarget(bits){
const exp=(bits>>>24)&0xff; const mant=BigInt(bits & 0x1d00ffff); if(exp<=3) return mant >> BigInt(8*(3-exp)); return mant << BigInt(8*(exp-3));
}
function hashToBigIntLE(h){
return BigInt("0x"+hex(Buffer.from(h).reverse()));
}
/* ================= Merkle ================= */
function merkleParent(a,b){ return dsha256(Buffer.concat([a,b])); }
function merkleRoot(leaves){
let level=leaves.slice();
while(level.length>1){
const next=[];
for(let i=0;i<level.length;i+=2){
const L=level[i];
const R=(i+1<level.length)?level[i+1]:level[i];
next.push(merkleParent(L,R));
}
level=next;
}
return level[0];
}
function merkleAuthPath(leaves,index){
let idx=index;
let level=leaves.slice();
const path=[];
while(level.length>1){
const sib=idx^1;
path.push((sib<level.length)?level[sib]:level[idx]);
const next=[];
for(let i=0;i<level.length;i+=2){
const L=level[i];
const R=(i+1<level.length)?level[i+1]:level[i];
next.push(merkleParent(L,R));
}
level=next;
idx=Math.floor(idx/2);
}
return path;
}
function recomputeRootFromPath(leaf,index,path){
let node=Buffer.from(leaf);
let idx=index;
for(const sib of path){
node=(idx&1) ? merkleParent(sib,node) : merkleParent(node,sib);
idx=Math.floor(idx/2);
}
return node;
}
/* ================= WOTS ================= */
const WOTS_N=32; const WOTS_W=16; const WOTS_LEN1=64; const WOTS_LEN2=3; const WOTS_LEN=67;
function wotsF(x){
return sha256(Buffer.concat([Buffer.from("WOTSF"),x]));
}
function wotsChain(x,steps){
let y=Buffer.from(x); for(let i=0;i<steps;i++) y=wotsF(y); return y;
}
function wotsSkElem(seed,leaf,i){
return sha256(Buffer.concat([ Buffer.from("WOTSSK"), seed, u32be(leaf), u32be(i) ]));
}
function baseW(digest){
const out=[];
for(let i=0;i<32;i++){
out.push((digest[i]>>>4)&15);
out.push(digest[i]&15);
}
return out;
}
function checksumDigits(digits){
let c=0;
for(const d of digits) c+=15-d;
const out=[0,0,0];
for(let i=2;i>=0;i--){
out[i]=c%16;
c=Math.floor(c/16);
}
return out;
}
function wotsDigits(digest){
const d=baseW(digest); return d.concat(checksumDigits(d));
}
function wotsPublicKeyHash(seed,leaf){
const parts=[];
for(let i=0;i<WOTS_LEN;i++){
const sk=wotsSkElem(seed,leaf,i);
parts.push(wotsChain(sk,15));
}
return sha256(Buffer.concat([Buffer.from("WOTSPK"),...parts]));
}
function wotsSign(seed,leaf,msg){
const digest=sha256(msg);
const digits=wotsDigits(digest);
const sig=Buffer.alloc(WOTS_LEN*WOTS_N);
for(let i=0;i<WOTS_LEN;i++){
const sk=wotsSkElem(seed,leaf,i);
const si=wotsChain(sk,digits[i]);
si.copy(sig,i*WOTS_N);
}
return {digest,sig};
}
/* ================= ECDSA Hybrid ================= */
function makeEcdsa(){
const kp=crypto.generateKeyPairSync("ec",{namedCurve:"secp256k1"});
const pubDer=kp.publicKey.export({type:"spki",format:"der"});
return {privateKey:kp.privateKey, publicKey:kp.publicKey, pubDer};
}
function ecdsaMessage(stdinHash,pqRoot,pqSigHash){
return dsha256(Buffer.concat([ Buffer.from("ECDSA-SIGN|PQH2|"), stdinHash, pqRoot, pqSigHash ]));
}
function signEcdsa(priv,msg){
return crypto.sign(null,msg,priv);
}
function verifyEcdsa(pubDer,msg,sig){
const pub=crypto.createPublicKey({key:pubDer,type:"spki",format:"der"}); return crypto.verify(null,msg,pub,sig);
}
function hybridHash(pqRoot,pqSigHash,ecdsaPubHash,ecdsaSigHash){
return dsha256(Buffer.concat([ Buffer.from("HYBRID|PQH2|"), pqRoot, pqSigHash, ecdsaPubHash, ecdsaSigHash ]));
}
/* ================= Bitcoin Scripts ================= */
function scriptP2PKH(h160){
return Buffer.concat([ Buffer.from([0x76,0xa9,0x14]), h160, Buffer.from([0x88,0xac]) ]);
}
function scriptOpReturnHybrid(hybrid){
const payload=Buffer.concat([Buffer.from("PQH2"),hybrid]); return Buffer.concat([Buffer.from([0x6a,0x24]),payload]);
}
/* ================= TX / Block ================= */
function buildCoinbaseTx({bits,msg,opret,reward,p2pkh}){
const prev=Buffer.alloc(32,0);
const vout=Buffer.from("ffffffff","hex");
const seq=Buffer.from("ffffffff","hex");
const bitsLE=u32le(bits);
const msgBuf=Buffer.from(msg,"utf8");
const scriptSig=Buffer.concat([
Buffer.from([4]),bitsLE,
varint(msgBuf.length),msgBuf
]);
const vin=Buffer.concat([
prev,
vout,
varint(scriptSig.length),
scriptSig,
seq
]);
const out0=Buffer.concat([u64le(0n),varint(opret.length),opret]);
const out1=Buffer.concat([u64le(reward),varint(p2pkh.length),p2pkh]);
const tx=Buffer.concat([
u32le(1),
Buffer.from([1]),vin,
Buffer.from([2]),out0,out1,
u32le(0)
]);
return {
tx,
txidInternal:dsha256(tx),
txidDisplay:Buffer.from(dsha256(tx)).reverse()
};
}
function mineHeader({merkle,time,bits}){
const target=bitsToTarget(bits);
let nonce=0;
while(true){
const header=Buffer.concat([
u32le(2),
Buffer.alloc(32,0),
merkle,
u32le(time),
u32le(bits),
u32le(nonce)
]);
const h=dsha256(header);
if(hashToBigIntLE(h)<=target){
return {nonce,header,hash:h,display:Buffer.from(h).reverse()};
}
nonce=(nonce+1)>>>0;
if(nonce===0) throw new Error("nonce wrapped");
}
}
/* ================= Verify ================= */
function verifyRecord(j){
const sigBlob=b64uDec(j.pq.signature_compressed_b64u);
const tag=sigBlob.slice(0,4).toString();
const leaf=sigBlob.readUInt32BE(4);
const leafPk=sigBlob.slice(8,40);
const digest=sigBlob.slice(40,72);
const wotsSig=sigBlob.slice(72,72+(WOTS_LEN*WOTS_N));
const auth=sigBlob.slice(72+(WOTS_LEN*WOTS_N));
const authNodes=[];
for(let i=0;i<auth.length;i+=32) authNodes.push(auth.slice(i,i+32));
const sigHash=dsha256(sigBlob);
const pqRoot=recomputeRootFromPath(leafPk,leaf,authNodes);
const pubDer=b64uDec(j.hybrid.ecdsa_pubkey_spki_der_b64u);
const ecdsaSig=b64uDec(j.hybrid.ecdsa_signature_der_b64u);
const stdinHash=Buffer.from(j.stdin.dsha256_hex,"hex");
const ecdsaMsg=ecdsaMessage(stdinHash,pqRoot,sigHash);
const ecdsaValid=verifyEcdsa(pubDer,ecdsaMsg,ecdsaSig);
const ecdsaPubHash=dsha256(Buffer.concat([Buffer.from("ECDSA-PUB|PQH2|"),pubDer]));
const ecdsaSigHash=dsha256(ecdsaSig);
const hybrid=hybridHash(pqRoot,sigHash,ecdsaPubHash,ecdsaSigHash);
const opret=Buffer.from(j.bitcoin.op_return_scriptpubkey_hex,"hex");
const opHybrid=opret.slice(6,38);
const tx=Buffer.from(j.bitcoin.coinbase_tx_hex,"hex");
const txid=Buffer.from(dsha256(tx)).reverse();
const header=Buffer.from(j.bitcoin.header_hex,"hex");
const blockHash=Buffer.from(dsha256(header)).reverse();
const bits=parseInt(j.bitcoin.bits_hex,16);
const powOk=hashToBigIntLE(dsha256(header))<=bitsToTarget(bits);
return {
verify:true,
signature_tag_ok:tag==="PQSW",
leaf_index_matches:leaf===j.pq.leaf_index,
signature_hash_matches:hex(sigHash)===j.pq.sig_hash_dsha256_hex,
pq_root_matches:hex(pqRoot)===j.pq.pq_root_hex,
ecdsa_signature_valid:ecdsaValid,
ecdsa_pubkey_hash_matches:hex(ecdsaPubHash)===j.hybrid.ecdsa_pubkey_hash_dsha256_hex,
ecdsa_signature_hash_matches:hex(ecdsaSigHash)===j.hybrid.ecdsa_signature_hash_dsha256_hex,
hybrid_hash_matches:hex(hybrid)===j.hybrid.hybrid_hash_dsha256_hex,
op_return_hybrid_matches:hex(opHybrid)===j.hybrid.hybrid_hash_dsha256_hex,
txid_matches:hex(txid)===j.bitcoin.coinbase_txid,
block_hash_matches:hex(blockHash)===j.bitcoin.block_hash,
proof_of_work_valid:powOk
};
}
/* ================= Build ================= */
function build(stdin){
const stdinHash=dsha256(Buffer.concat([Buffer.from("STDIN|PQ|"),stdin]));
const seed=stdinHash;
const height=4;
const leafCount=1<<height;
const leafIndex=stdinHash[0]%leafCount;
const leaves=[];
for(let i=0;i<leafCount;i++) leaves.push(wotsPublicKeyHash(seed,i));
const pqRoot=merkleRoot(leaves);
const auth=merkleAuthPath(leaves,leafIndex);
const authBuf=Buffer.concat(auth);
const pqMsg=sha256(Buffer.concat([
Buffer.from("PQGEN|"),
stdinHash,
Buffer.from("|"),
pqRoot
]));
const signed=wotsSign(seed,leafIndex,pqMsg);
const leafPk=leaves[leafIndex];
const sigBlob=Buffer.concat([
Buffer.from("PQSW"),
u32be(leafIndex),
leafPk,
signed.digest,
signed.sig,
authBuf
]);
const pqSigHash=dsha256(sigBlob);
const ecdsa=makeEcdsa();
const ecdsaMsg=ecdsaMessage(stdinHash,pqRoot,pqSigHash);
const ecdsaSig=signEcdsa(ecdsa.privateKey,ecdsaMsg);
const ecdsaPubHash=dsha256(Buffer.concat([
Buffer.from("ECDSA-PUB|PQH2|"),
ecdsa.pubDer
]));
const ecdsaSigHash=dsha256(ecdsaSig);
const hybrid=hybridHash(pqRoot,pqSigHash,ecdsaPubHash,ecdsaSigHash);
const opret=scriptOpReturnHybrid(hybrid);
const p2pkhHash=sha256(Buffer.concat([Buffer.from("P2PKH|"),stdin])).subarray(0,20);
const p2pkh=scriptP2PKH(p2pkhHash);
const bits=0x1d00ffff;
const time=(Math.floor(Date.now()/1000))>>>0;
const reward=5000000000n;
const tx=buildCoinbaseTx({
bits,
msg:`PQ-HYBRID|${new Date(time*1000).toISOString()}`,
opret,
reward,
p2pkh
});
const mined=mineHeader({merkle:tx.txidInternal,time,bits});
const block=Buffer.concat([mined.header,Buffer.from([1]),tx.tx]);
return {
schema:"pq_genesis_block_stdin/hybrid-wots-ecdsa-v1",
stdin:{
bytes:stdin.length,
dsha256_hex:hex(stdinHash)
},
pq:{
scheme:"wots-sha256-merkle",
params:{n:32,w:16,len:67,len1:64,len2:3,height},
leaf_index:leafIndex,
pq_root_hex:hex(pqRoot),
message_sha256_hex:hex(pqMsg),
wots_digest_hex:hex(signed.digest),
signature_len_bytes:sigBlob.length,
signature_compressed_b64u:b64u(sigBlob),
sig_hash_dsha256_hex:hex(pqSigHash),
auth_path_hex:auth.map(hex)
},
hybrid:{
scheme:"ecdsa-secp256k1+wots-sha256-merkle",
op_return_version:"PQH2",
ecdsa_pubkey_spki_der_b64u:b64u(ecdsa.pubDer),
ecdsa_pubkey_hash_dsha256_hex:hex(ecdsaPubHash),
ecdsa_signature_der_b64u:b64u(ecdsaSig),
ecdsa_signature_hash_dsha256_hex:hex(ecdsaSigHash),
hybrid_hash_dsha256_hex:hex(hybrid)
},
bitcoin:{
bits_hex:bits.toString(16),
time_unix:time,
op_return_scriptpubkey_hex:hex(opret),
coinbase_tx_hex:hex(tx.tx),
coinbase_txid:hex(tx.txidDisplay),
merkle_root_display:hex(tx.txidDisplay),
header_hex:hex(mined.header),
block_hash:hex(mined.display),
nonce:mined.nonce,
full_block_hex:hex(block),
full_block_bytes:block.length
}
};
}
/* ================= Main ================= */
(async()=>{
const input=await readStdin();
if(input.length===0) throw new Error("No STDIN");
let json=null;
try{ json=JSON.parse(input.toString()); }catch{}
if(json && json.schema){
console.log(JSON.stringify(verifyRecord(json),null,2));
}else{
console.log(JSON.stringify(build(input),null,2));
}
})();