Skip to content

EhudKirsh/JavaScript-SHA256-and-RIPEMD160-hashing-functions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 

Repository files navigation

The implementations of SHA256 and RIPEMD160 below work on inputs that are smaller than 2 to the power of 30 (1<<30) bytes or 1GB.

'use strict'

// sha256 in JS using the inbuilt crypto API for high performance

const sha256=async(s,isHex)=>{/* Input a string '' s of any length. Leave isHex blank to input a UTF-8 Text s and output a hexadecimal 64-characters long hash.
        Enter isHex as true to input a hexadecimal s and again also output a hexadecimal 64-characters long hash.
        Enter isHex as false to input a binary s and output a binary 256-characters long hash.
    */
    let inputBuffer
    if(isHex===false){// The s input is binary
        if(s.length!==0&&!/^[01]+$/.test(s))return'Only enter binary characters 0 & 1 for the message!'
        const remainder=s.length&7;let s_in=remainder!==0?'0'.repeat(8-remainder)+s:s
        const l=s_in.length>>3,bytes=new Uint8Array(l)
        for(let i=-1;++i<l;)bytes[i]=parseInt(s_in.substring(i<<3,(i<<3)+8),2)
        inputBuffer=bytes
    }else if(isHex===true){// The s input is hexadecimal
        if(s.length!==0&&!/^[0-9A-Fa-f]+$/.test(s))return'Only enter hexadecimal characters 0-9 & A-F!'
        let s_in=s.length%2!==0?'0'+s:s;const l=s_in.length/2,bytes=new Uint8Array(l)
        for(let i=-1;++i<l;)bytes[i]=parseInt(s_in.substring(i*2,i*2+2),16)
        inputBuffer=bytes
    }else // The s input is a generic text
        inputBuffer=new TextEncoder().encode(s)

    const view=new DataView(await crypto.subtle.digest('SHA-256',inputBuffer)),l=view.byteLength;let digest='',i=0

    if(isHex===false)
        do{digest+=view.getUint8(i).toString(2).padStart(8,'0')}while(++i<l)
    else
        do{digest+=('00000000'+view.getUint32(i).toString(16)).slice(-8);i+=4}while(i<l)

    return digest
}

/* e.g.
    await sha256('') OR await sha256('',true) //➜ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
    await sha256('',false) //➜ '1110001110110000110001000100001010011000111111000001110000010100100110101111101111110100110010001001100101101111101110010010010000100111101011100100000111100100011001001001101110010011010011001010010010010101100110010001101101111000010100101011100001010101'

    await sha256('Hello World') //➜ 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'
    await sha256('Try') //➜ '85d6c0718cfecca1bd0f61cb166ad7982f506e1acd6eb6938727aabe5f050c6d'
    await sha256('Hash me if you can!') //➜ '0b4d49ac986d537f077b140ea3a9d1cf46799b25b4034764c1567b6f392b0902'
    await sha256('🙂') //➜ 'd06f1525f791397809f9bc98682b5c13318eca4c3123433467fd4dffda44fd14'
    await sha256(await sha256('Satoshi LOVED to hash shit!!1')) //➜ 'c61b125dc5b0312ba98b602d4c10324aa83a42494c2e259a2ee842cb3c0f403d'

    await sha256('524A5567F067C0E5C9BC9044C5A0518687737B2FDDE91D0D6A1FFCCEB3F2E0A1',true)
        //➜ '53c302973550844b25843eecddff40dd11a65ce9a3cec71b4e35ca3e084d591c'

    await sha256('137440F7D9DE62840F90D34769AAF48DBC78D19EB8EA9C02836D1D9FDC93091E',true)
        //➜ '1dd9319bc6db324903ff0f13dda181d256ac862f4bec91b21a58b35177c076f7'

    await sha256('0101001001001010010101010110011111110000011001111100000011100101110010011011110010010000010001001100010110100000010100011000011010000111011100110111101100101111110111011110100100011101000011010110101000011111111111001100111010110011111100101110000010100001',false)
    //➜ '0101001111000011000000101001011100110101010100001000010001001011001001011000010000111110111011001101110111111111010000001101110100010001101001100101110011101001101000111100111011000111000110110100111000110101110010100011111000001000010011010101100100011100'
*/

// if you only want to hash text, like the contents of regular files. Basically leaving isHex blank in sha256 above.
,sha256text=async s=>{
    const view=new DataView(await crypto.subtle.digest('SHA-256',new TextEncoder().encode(s)))
    ,l=view.byteLength;let digest='',i=0
    do{digest+=('00000000'+view.getUint32(i).toString(16)).slice(-8);i+=4}while(i<l)
    return digest
}/* e.g.
    await sha256text('') //➜ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
    await sha256text('Hello World') //➜ 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'
    await sha256text('Try') //➜ '85d6c0718cfecca1bd0f61cb166ad7982f506e1acd6eb6938727aabe5f050c6d'
    await sha256text('Hash me if you can!') //➜ '0b4d49ac986d537f077b140ea3a9d1cf46799b25b4034764c1567b6f392b0902'
    await sha256text('🙂') //➜ 'd06f1525f791397809f9bc98682b5c13318eca4c3123433467fd4dffda44fd14'
    await sha256text(await sha256text('Satoshi LOVED to hash shit!!1')) //➜ 'c61b125dc5b0312ba98b602d4c10324aa83a42494c2e259a2ee842cb3c0f403d'
*/


// Custom implementation of SHA256 in JS without the inbuilt crypto API to prove it's possible, but probably less performant
,RotateRight=(v,n)=>v>>>n|v<<32-n // v = value, n = number of bits

,SHA256=(s,isHex)=>{/* Input a string '' s of any length. Leave isHex blank to input a UTF-8 Text s and output a hexadecimal 64-characters long hash.
        Enter isHex as true to input a hexadecimal s and again also output a hexadecimal 64-characters long hash.
        Enter isHex as false to input a binary s and output a binary 256-characters long hash.
    */
    let B,asciiBitLength

    if(isHex===false){// the s input is binary
        if(s.length!==0&&!/^[01]+$/.test(s))return'Only enter binary characters 0 & 1 for the message!'
        asciiBitLength=s.length;B=s;if(B.length&7)B='0'.repeat(8-(B.length&7))+B
    }else if(isHex===true){// the s input is hexadecimal
        if(s.length!==0&&!/^[0-9A-Fa-f]+$/.test(s))return'Only enter hexadecimal characters 0-9 & A-F!'
        let hex=s;asciiBitLength=hex.length*4;B='';if(hex.length&1)hex='0'+hex
        const hexLen=hex.length;for(let i=0;i<hexLen;i+=2)B+=parseInt(hex.substring(i,i+2),16).toString(2).padStart(8,'0')
    }else{// the s input is a generic text
        B='';const sLen=s.length
        for(let i=0;i<sLen;){
            let code=s.charCodeAt(i++)
            if(code<128)B+=code.toString(2).padStart(8,'0')
            else if(code<2048){
                B+=(192|code>>6).toString(2).padStart(8,'0');B+=(128|(code&63)).toString(2).padStart(8,'0')
            }else if(code<55296||code>=57344){
                B+=(224|code>>12).toString(2).padStart(8,'0');B+=(128|code>>6&63).toString(2).padStart(8,'0')
                B+=(128|(code&63)).toString(2).padStart(8,'0')
            }else{
                code=65536+(((code&1023)<<10)|(s.charCodeAt(i++)&1023))
                B+=(240|code>>18).toString(2).padStart(8,'0');B+=(128|code>>12&63).toString(2).padStart(8,'0')
                B+=(128|code>>6&63).toString(2).padStart(8,'0');B+=(128|(code&63)).toString(2).padStart(8,'0')
            }
        }
        asciiBitLength=B.length
    }
    const words=[];let i,j,HashValue='',h=SHA256.h=SHA256.h||[],k=SHA256.k=SHA256.k||[],primeCounter=k.length
    if(primeCounter<64){
        const isComposite={}
        for(let candidate=2;primeCounter<64;++candidate){
            if(!isComposite[candidate]){
                for(i=0;i<313;i+=candidate)isComposite[i]=candidate
                h[primeCounter]=Math.sqrt(candidate)*4294967296|0
                k[primeCounter++]=Math.cbrt(candidate)*4294967296|0
            }
        }
    }
    let bits=B+'10000000',hash=h.slice(0,8);bits+='0'.repeat(960-(bits.length&511)&511)
    bits+=(asciiBitLength/4294967296|0).toString(2).padStart(32,'0');bits+=(asciiBitLength>>>0).toString(2).padStart(32,'0')
    const bitsLen=bits.length;for(i=0;i<bitsLen;i+=32)words.push(parseInt(bits.slice(i,i+32),2)|0)
    const wordsLen=words.length
    for(j=0;j<wordsLen;){
        const oldHash=hash;let w=words.slice(j,j+=16),[a,b,c,d,e,f,g,h]=oldHash
        for(i=-1;++i<64;){
            const w15=w[i-15],w2=w[i-2],s1=RotateRight(e,6)^RotateRight(e,11)^RotateRight(e,25),ch=e&f^~e&g
            ,w_i=i<16?w[i]:w[i]=w[i-16]+(RotateRight(w15,7)^RotateRight(w15,18)^w15>>>3)+w[i-7]+(RotateRight(w2,17)^RotateRight(w2,19)^w2>>>10)|0
            ,temp1=h+s1+ch+k[i]+w_i|0,s0=RotateRight(a,2)^RotateRight(a,13)^RotateRight(a,22)
            ,maj=a&b^a&c^b&c,temp2=s0+maj|0

            h=g;g=f;f=e;e=d+temp1|0;d=c;c=b;b=a;a=temp1+temp2|0
        }
        hash[0]=oldHash[0]+a|0;hash[1]=oldHash[1]+b|0;hash[2]=oldHash[2]+c|0;hash[3]=oldHash[3]+d|0
        hash[4]=oldHash[4]+e|0;hash[5]=oldHash[5]+f|0;hash[6]=oldHash[6]+g|0;hash[7]=oldHash[7]+h|0
    }
    if(isHex===false)
        for(i=-1;++i<8;)HashValue+=(hash[i]>>>0).toString(2).padStart(32,'0')
    else
        for(i=-1;++i<8;)HashValue+=(hash[i]>>>0).toString(16).padStart(8,'0')

    return HashValue
}/* e.g.
    SHA256('') OR SHA256('',true) //➜ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
    SHA256('',false) //➜ '1110001110110000110001000100001010011000111111000001110000010100100110101111101111110100110010001001100101101111101110010010010000100111101011100100000111100100011001001001101110010011010011001010010010010101100110010001101101111000010100101011100001010101'

    SHA256('Hello World') //➜ 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'
    SHA256('Try') //➜ '85d6c0718cfecca1bd0f61cb166ad7982f506e1acd6eb6938727aabe5f050c6d'
    SHA256('Hash me if you can!') //➜ '0b4d49ac986d537f077b140ea3a9d1cf46799b25b4034764c1567b6f392b0902'
    SHA256('🙂') //➜ 'd06f1525f791397809f9bc98682b5c13318eca4c3123433467fd4dffda44fd14'
    SHA256(SHA256('Satoshi LOVED to hash shit!!1')) //➜ 'c61b125dc5b0312ba98b602d4c10324aa83a42494c2e259a2ee842cb3c0f403d'

    SHA256('524A5567F067C0E5C9BC9044C5A0518687737B2FDDE91D0D6A1FFCCEB3F2E0A1',true)
        //➜ '53c302973550844b25843eecddff40dd11a65ce9a3cec71b4e35ca3e084d591c'

    SHA256('137440F7D9DE62840F90D34769AAF48DBC78D19EB8EA9C02836D1D9FDC93091E',true)
        //➜ '1dd9319bc6db324903ff0f13dda181d256ac862f4bec91b21a58b35177c076f7'

SHA256('0101001001001010010101010110011111110000011001111100000011100101110010011011110010010000010001001100010110100000010100011000011010000111011100110111101100101111110111011110100100011101000011010110101000011111111111001100111010110011111100101110000010100001',false)
    //➜ '0101001111000011000000101001011100110101010100001000010001001011001001011000010000111110111011001101110111111111010000001101110100010001101001100101110011101001101000111100111011000111000110110100111000110101110010100011111000001000010011010101100100011100'
*/

/* Credit: https://geraintluff.github.io/sha256
    I take some credit here for cleaning up the original version by geraintluff and adding features:
        - Expanded the possible text input from only the 256 1-byte UTF-8 characters to include every letter of other languages, symbols and emojis
        - Added a 2nd input to select between a UTF-8 text input, hexadecimal and binary
        - made RotateRight into its own function instead of defining it every time SHA256 is used inside of it.
            Now both are constant arrow functions, so they save bytes when minified instead of writing 'function' twice.
            They are also be garbage collected now that they are constants.
        - Replaced % with &
        - used .repeat() instead of some loops
        - var instead of few let and const. It also provides better memory management and garbage collection.
        - useless brackets ( ) in places where the order of operations made them pointless.
        - removed obvious comments.
        - removed a variable that wasn't even used.
        - made use of Math.sqrt and Math.cbrt.
        - put -- and ++ before, not after. This saves a temporary variable each time.
        - pre-calculated maxWord as 4294967296.
        - used .length to be clearer when measuring length.
        - stopped any and all unnecessary .length measurements, especially those done in loops.
*/


/* Unlike sha256 that is supported by the Crypto API in any JS runtime environment (HTML, NodeJS, Deno & Bun),
    ripemd160 is only supported by the Crypto API in NodeJS & Bun. Deno follows the browser specs by design.
    Below is the ripemd160 implementation using the Crypto API in NodeJS:
*/
,crypto=require('crypto')
,ripemd160=(s,isHex)=>{/* Input a string '' s of any length. Leave isHex blank to input a UTF-8 Text s and output a hexadecimal 40-characters long hash.
        Enter isHex as true to input a hexadecimal s and again also output a hexadecimal 40-characters long hash.
        Enter isHex as false to input a binary s and output a binary 160-characters long hash.
    */
    const hash=crypto.createHash('ripemd160') // only available in NodeJS and Bun, NOT in the web crypto API of HTML on browsers or in Deno.

    if(isHex===false){// the s input is binary
        if(s.length!==0&&!/^[01]+$/.test(s))return'Only enter binary characters 0 & 1 for the message!'
        const s_in=(s.length&7)!==0?'0'.repeat(8-(s.length&7))+s:s
        ,l=s_in.length>>3,bytes=Buffer.alloc(l)
        for(let i=-1;++i<l;)bytes[i]=parseInt(s_in.substring(i<<3,(i<<3)+8),2)
        hash.update(bytes)
    }else if(isHex===true){// the s input is hexadecimal
        if(s.length!==0&&!/^[0-9A-Fa-f]+$/.test(s))return'Only enter hexadecimal characters 0-9 & A-F!'
        hash.update(s,'hex')
    }else // the s input is a generic text
        hash.update(s,'utf8')

    if(isHex===false){
        const digestBuffer=hash.digest();let HashValue='',i=0
        do{HashValue+=digestBuffer[i].toString(2).padStart(8,'0')}while(++i<20)
        return HashValue
    }// else
        return hash.digest('hex')
}


// Below is a custom implementation of RIPEMD160 independent of the NodeJS crypto API.
,RotateLeft=(v,n)=>v<<n|v>>>32-n>>>0 // v=value, n=number of bits
,C={ // magic constants
	f:[(x,y,z)=>x^y^z,(x,y,z)=>(x&y)|(~x&z),(x,y,z)=>(x|~y)^z,(x,y,z)=>(x&z)|(y&~z),(x,y,z)=>x^(y|~z)],
	K:[0,1518500249,1859775393,2400959708,2840853838],KK:[1352829926,1548603684,1836072691,2053994217,0],
	R:[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],[7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8],[3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12],[1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2],[4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]],
	S:[[11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8],[7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12],[11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5],[11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12],[9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]],
	RR:[[5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12],[6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2],[15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13],[8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14],[12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]],
	SS:[[8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6],[9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11],[9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5],[15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8],[8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]]
}
,RIPEMD160=(s,isHex)=>{
	let bytes,asciiBitLength
	if(isHex===false){
		if(s.length!==0&&!/^[01]+$/.test(s))return'Only enter binary characters 0 & 1 for the message!'
		asciiBitLength=s.length;let B=s
		if(B.length&7)B='0'.repeat(8-(B.length&7))+B
		const l=B.length>>3;bytes=new Uint8Array(l)
		for(let i=0;i<l;++i)bytes[i]=parseInt(B.substring(i<<3,(i<<3)+8),2)
	}else if(isHex===true){
		if(s.length!==0&&!/^[0-9A-Fa-f]+$/.test(s))return'Only enter hexadecimal characters 0-9 & A-F!'
		let hex=s.length&1?'0'+s:s;asciiBitLength=hex.length*4;const l=hex.length>>1;bytes=new Uint8Array(l)
		for(let i=0;i<l;++i)bytes[i]=parseInt(hex.substr(i<<1,2),16)
	}else{
		bytes=new TextEncoder().encode(s);asciiBitLength=bytes.length*8
	}
	let l=bytes.length,rem=(l+8)&63,padLen=rem===0?64:64-rem,msg=new Uint8Array(l+padLen+8)
	msg.set(bytes);msg[l]=128;let bitLen=asciiBitLength>>>0,hiLen=(asciiBitLength/4294967296)>>>0
	for(let i=0;i<4;++i)msg[l+padLen+i]=bitLen>>>8*i&255;for(let i=0;i<4;++i)msg[l+padLen+4+i]=hiLen>>>8*i&255
	let h=[1732584193,4023233417,2562383102,271733878,3285377520]
	for(let i=0;i<msg.length;i+=64){
		let X=[];for(let j=0;j<64;j+=4)X.push(msg[i+j]|msg[i+j+1]<<8|msg[i+j+2]<<16|msg[i+j+3]<<24)
		let[al,bl,cl,dl,el]=h,[ar,br,cr,dr,er]=h
		for(let j=0;j<80;++j){
			let r=j>>4,sj=j&15,t=(RotateLeft(al+C.f[r](bl,cl,dl)+X[C.R[r][sj]]+C.K[r],C.S[r][sj])+el)>>>0
			al=el;el=dl;dl=RotateLeft(cl,10);cl=bl;bl=t
			let tt=(RotateLeft(ar+C.f[4-r](br,cr,dr)+X[C.RR[r][sj]]+C.KK[r],C.SS[r][sj])+er)>>>0
			ar=er;er=dr;dr=RotateLeft(cr,10);cr=br;br=tt
		}
		let t=(h[1]+cl+dr)>>>0;h[1]=(h[2]+dl+er)>>>0;h[2]=(h[3]+el+ar)>>>0;h[3]=(h[4]+al+br)>>>0;h[4]=(h[0]+bl+cr)>>>0;h[0]=t
	}
	h=h.map(x=>(x&255).toString(16).padStart(2,'0')+(x>>>8&255).toString(16).padStart(2,'0')+(x>>>16&255).toString(16).padStart(2,'0')+(x>>>24&255).toString(16).padStart(2,'0'))
	return isHex===false?h.map(x=>x.match(/../g).map(b=>parseInt(b,16).toString(2).padStart(8,'0')).join('')).join(''):h.join('')
}/* e.g. examples from https://en.wikipedia.org/wiki/RIPEMD:
    RIPEMD160('') OR RIPEMD160('',true) //➜ '9c1185a5c5e9fc54612808977ee8f548b2258d31'
    RIPEMD160('',false) //➜ '1001110000010001100001011010010111000101111010011111110001010100011000010010100000001000100101110111111011101000111101010100100010110010001001011000110100110001'

    RIPEMD160('The quick brown fox jumps over the lazy dog') //➜ '37f332f68db77bd9d7edd4969571ad671cf9dd3b'
    RIPEMD160('The quick brown fox jumps over the lazy cog') //➜ '132072df690933835eb8b6ad0b77e7b6f14acad7'
*/


// Bech32: Use it with RIPEMD160 & SHA256 to generate a Bitcoin BIP84 P2WPKH Native SegWit 42-characters long address from a public key
,CHARSET='qpzry9x8gf2tvdw0s3jn54khce6mua7l'
,GENERATOR=[996825010,642813549,513874426,1027748829,705979059]
,PolyMod=(v,chk)=>{
    const top=chk>>25;chk=(chk&0x1ffffff)<<5^v
    for(let i=-1;++i<5;)if(top>>i&1)chk^=GENERATOR[i]
    return chk
}
,Bech32=pubkeyHash=>{
    const hashLen=pubkeyHash.length
    let acc=0,bits=0,data=[0],chk=1,encodedParts=[]

    for(let i=0;i<hashLen;i+=2){
        acc=acc<<8|parseInt(pubkeyHash.substr(i,2),16);bits+=8
        while(bits>=5){bits-=5;data.push(acc>>bits&31)}
    }
    bits>0&&data.push(acc<<5-bits&31)

    for(let i=-1;++i<2;)chk=PolyMod('bc'.charCodeAt(i)>>5,chk);chk=PolyMod(0,chk)
    for(let i=-1;++i<2;)chk=PolyMod('bc'.charCodeAt(i)&31,chk);const dataLen=data.length
    for(let i=-1;++i<dataLen;)chk=PolyMod(data[i],chk);for(let i=-1;++i<6;)chk=PolyMod(0,chk)

    chk^=1
    for(let i=-1;++i<dataLen;)encodedParts.push(CHARSET[data[i]])
    for(let i=-1;++i<6;)encodedParts.push(CHARSET[chk>>5*(5-i)&31])

    return 'bc1'+encodedParts.join('')
}
/* e.g. P2WPKH addresses of the 24 all-bacon mnemonic. Find these pubkeys in Electrum and Sparrow:
    m/84'/0'/0'/0/0: Bech32(RIPEMD160(await sha256('03a373adbadeb5bad03469464fab4a208ea555e41988bff25acd15977f0e998b30',true),true))
        //➜ 'bc1q8fxfd8jg6s9y66ydpxhmqaaw65f8qeg5huynyq'
    m/84'/0'/0'/1/9: Bech32(RIPEMD160(await sha256('039b2117c54660d84311261b3adacdc3ebada3d3b0c9f60e3bc113378dc876870d',true),true))
        //➜ 'bc1q8u8cck64u9q6498aysq5pt65tg0r24e5yeqw08'
*/

About

Custom & Crypto API, UTF-8 text, Hexadecimal & Binary implementations

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published