浅析莱特币的Scrypt哈希算法

Scrypt算法简介

Scrypt是内存依赖型的POW算法,莱特币采用此算法。第一个使用Scrypt算法的数字货币是Tenebrix,而后该算法被莱特币使用。莱特币创始人在莱特币创世帖中介绍了莱特币采用的共识机制,挖矿算法,发行总量,挖矿难度等相关重要信息。李启威说明了莱特币所使用的挖矿算法为数字货币Tenebrix所使用的Scrypt算法,是一种符合PoW共识机制的算法。Scrypt算法过程中也需要计算哈希值,但是,Scrypt计算过程中需要使用较多的内存资源。

其它使用Scrypt算法的数字货币还有数码币(DigitalCoin)、狗狗币(DogeCoin)、幸运币(LuckyCoin)、世界币(WorldCoin)等。

Scrypt的诞生

由于比特币将hash算法作为pow工作量证明的重要手段,后续的各种采用pow的数字货币也延续了这个设计,以SHA256、MD5(MD5后来被证明不具备强碰撞性数字货币一般不用)为代表算法在设计之初属于算法都是算力敏感型,意味着计算资源是瓶颈,主频越高的 CPU 进行 Hash 的速度也越快。这个设计直接导致后来的矿机出现,采用ASIC芯片的矿机更是将这种运算能力成倍提升,更多矿场的出现使得当时的比特币面临算力中心化的威胁(关于矿场这个争论现在依旧)

为了限制计算能力的依赖,人们开始寻求新的算法,我们说过现代计算机是“存储转发结构”,既然要限制转发CPU的能力,目光自然投向存储依赖,也就是内存依赖,莱特币率先使用内存依赖型的scrypt算法作为pow核心,因此奠定了第一山寨币的地位。

scrpyt算法是由著名的FreeBSD黑客 Colin Percival为他的备份服务 Tarsnap开发的,当初的设计是为了降低CPU负荷,尽量少的依赖cpu计算,利用CPU闲置时间进行计算,因此scrypt不仅计算所需时间长,而且占用的内存也多,使得并行计算多个摘要异常困难,因此利用rainbow table进行暴力攻击更加困难。scrypt没有在生产环境中大规模应用,并且缺乏仔细的审察和广泛的函数库支持。所以scrpyt一直没有推广开,但是由于其内存依赖的设计特别符合当时对抗专业矿机的设计,成为数字货币算法发展的一个主要应用方向。

后来有人在SCRYPT的基础上稍作修改形成Scrypt –N算法,改进思路都一样,都是追求更大的内存消耗和计算时间,以有效阻止ASIC专用矿机。

Scrypt算法来源

scrypt是由著名的FreeBSD黑客 Colin Percival为他的备份服务 Tarsnap开发的。刚开始只是用于防止网络攻击用的,但是后来逐渐延用到虚拟货币的技术上。

Scrypt算法有哪些好处?

scrypt加密算法不仅计算所需时间长,而且占用的内存也多,使得并行计算多个摘要异常困难,因此利用rainbow table进行暴力攻击更加困难。scrypt没有在生产环境中大规模应用,并且缺乏仔细的审察和广泛的函数库支持。但是,scrypt在算法层面只要没有破绽,它的安全性应该高于PBKDF2和bcrypt。并且以特币将scrypt算法用于挖矿算法中,将scrypt算法的优势充分发挥出来。

在经历了比特币风波之后,投资者对于虚拟货币的安全是越来越重视。而以特币现在采用的scrypt加密算法却刚好满足了交易者的需求,不仅实现了交易安全保护,而且实现了匿名交易,能够最大程度上保护交易者的隐私。

以特币的scrypt加密算法使得以特币的交易自主性、独立性增强,能够不受任何政府和组织的控制,进一步的维护了以特币的交易安全。

而以特币的这一优点也是比特币所不具备的。当中国宣布比特币退出中国市场的交易时,比特币意味着失去了百分之六十的市场,比特币的这一失足也给虚拟货币的发展泼了一盆冷水。但是以特币对于scrypt算法的运用却完全消除了这一弊端。scrept算法为虚拟货币的发展注入了新活力,也为以特币的进一步发展打下了良好的基础。

Scrpyt后续影响

虽然后来诞生的FPGA矿机可以有效运行scrpyt算法进行大规模挖矿,(FPGA和ASIC类似,FGPA板每块有高达24.8GB/s或以上的内存带宽,属于专用可编程芯片,其实显卡挖矿也是利用了显卡GPU计算能力和内存带宽能力),但是scrpyt算法的使用开启了人们对于hash计算去中心化安全的探索。

后来诞生过串行算法,并行算法等等,其中X11就是使用了11种加密算法(BLAKE, BMW, GROESTL, JH, KECCAK, SKEIN, LUFFA, CUBEHASH, SHAVITE, SIMD, ECHO)

不要被这些算法吓到,其实就是对一段数据进行了11次不同算法的运算,通过运算复杂度增强安全性和加大计算资源消耗,一方面更安全,另一方面抵抗专业硬件编程矿机。后来认为这种串行方式一个算法被攻破实际上会导致整个体系安全性受到攻击,又发展出并行算法。

所谓并行就是将数据进行分块,比如切分成11份,每一份使用不同算法计算,再拼接不同的结算结果而已。

由于pow算法依赖计算资源,不论是CPU或者是内存依赖,到后期总会有更专门的硬件出现,这是计算机体系结构决定的,后期诞生的币种或相关项目将目光投向pos证明机制,不再依赖算力,这也是发展的必然结果,虽然目前pos机制还在发展中,但是未来这是抵御中心化算力更好的方式。

scrpyt的价值在于提醒了人们对于算力中心化的认识,促进了区块链系统向更好的方向发展,我认为这是一个里程碑式的算法,估计该算法的发明人也没有想到scrpyt在区块链世界是那么的重要。

使用POM


    com.lambdaworks
    scrypt
    1.4.0

Java实现:

import com.lambdaworks.jni.*;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;

import static java.lang.Integer.MAX_VALUE;
import static java.lang.System.arraycopy;


public class SCrypt {
    private static final boolean native_library_loaded;

    static {
        LibraryLoader loader = LibraryLoaders.loader();
        native_library_loaded = loader.load("scrypt", true);
    }

    /**
     
     * @param passwd    Password.
     * @param salt      Salt.
     * @param N         CPU cost parameter.
     * @param r         Memory cost parameter.
     * @param p         Parallelization parameter.
     * @param dkLen     Intended length of the derived key.
     *
     /
    public static byte[] scrypt(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException {
        return native_library_loaded ? scryptN(passwd, salt, N, r, p, dkLen) : scryptJ(passwd, salt, N, r, p, dkLen);
    }

    /**
     * Native C implementation of the scrypt KDF using
     * the code from http://www.tarsnap.com/scrypt.html.
     *
     * @param passwd    Password.
     * @param salt      Salt.
     * @param N         CPU cost parameter.
     * @param r         Memory cost parameter.
     * @param p         Parallelization parameter.
     * @param dkLen     Intended length of the derived key.
     *
     * @return The derived key.
     */
    public static native byte[] scryptN(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen);

    /**
     * Pure Java implementation of the scrypt KDF.
     *
     * @param passwd    Password.
     * @param salt      Salt.
     * @param N         CPU cost parameter.
     * @param r         Memory cost parameter.
     * @param p         Parallelization parameter.
     * @param dkLen     Intended length of the derived key.
     *
     * @return The derived key.
     *
     * @throws GeneralSecurityException when HMAC_SHA256 is not available.
     */
    public static byte[] scryptJ(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException {
        if (N < 2 || (N & (N - 1)) != 0) throw new IllegalArgumentException("N must be a power of 2 greater than 1");

        if (N > MAX_VALUE / 128 / r) throw new IllegalArgumentException("Parameter N is too large");
        if (r > MAX_VALUE / 128 / p) throw new IllegalArgumentException("Parameter r is too large");

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(passwd, "HmacSHA256"));

        byte[] DK = new byte[dkLen];

        byte[] B  = new byte[128 * r * p];
        byte[] XY = new byte[256 * r];
        byte[] V  = new byte[128 * r * N];
        int i;

        PBKDF.pbkdf2(mac, salt, 1, B, p * 128 * r);

        for (i = 0; i < p; i++) {
            smix(B, i * 128 * r, r, N, V, XY);
        }

        PBKDF.pbkdf2(mac, B, 1, DK, dkLen);

        return DK;
    }

    public static void smix(byte[] B, int Bi, int r, int N, byte[] V, byte[] XY) {
        int Xi = 0;
        int Yi = 128 * r;
        int i;

        arraycopy(B, Bi, XY, Xi, 128 * r);

        for (i = 0; i < N; i++) {
            arraycopy(XY, Xi, V, i * (128 * r), 128 * r);
            blockmix_salsa8(XY, Xi, Yi, r);
        }

        for (i = 0; i < N; i++) {
            int j = integerify(XY, Xi, r) & (N - 1);
            blockxor(V, j * (128 * r), XY, Xi, 128 * r);
            blockmix_salsa8(XY, Xi, Yi, r);
        }

        arraycopy(XY, Xi, B, Bi, 128 * r);
    }

    public static void blockmix_salsa8(byte[] BY, int Bi, int Yi, int r) {
        byte[] X = new byte[64];
        int i;

        arraycopy(BY, Bi + (2 * r - 1) * 64, X, 0, 64);

        for (i = 0; i < 2 * r; i++) {
            blockxor(BY, i * 64, X, 0, 64);
            salsa20_8(X);
            arraycopy(X, 0, BY, Yi + (i * 64), 64);
        }

        for (i = 0; i < r; i++) {
            arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64);
        }

        for (i = 0; i < r; i++) {
            arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64);
        }
    }

    public static int R(int a, int b) {
        return (a << b) | (a >>> (32 - b));
    }

    public static void salsa20_8(byte[] B) {
        int[] B32 = new int[16];
        int[] x   = new int[16];
        int i;

        for (i = 0; i < 16; i++) {
            B32[i]  = (B[i * 4 + 0] & 0xff) << 0;
            B32[i] |= (B[i * 4 + 1] & 0xff) << 8;
            B32[i] |= (B[i * 4 + 2] & 0xff) << 16;
            B32[i] |= (B[i * 4 + 3] & 0xff) << 24;
        }

        arraycopy(B32, 0, x, 0, 16);

        for (i = 8; i > 0; i -= 2) {
            x[ 4] ^= R(x[ 0]+x[12], 7);  x[ 8] ^= R(x[ 4]+x[ 0], 9);
            x[12] ^= R(x[ 8]+x[ 4],13);  x[ 0] ^= R(x[12]+x[ 8],18);
            x[ 9] ^= R(x[ 5]+x[ 1], 7);  x[13] ^= R(x[ 9]+x[ 5], 9);
            x[ 1] ^= R(x[13]+x[ 9],13);  x[ 5] ^= R(x[ 1]+x[13],18);
            x[14] ^= R(x[10]+x[ 6], 7);  x[ 2] ^= R(x[14]+x[10], 9);
            x[ 6] ^= R(x[ 2]+x[14],13);  x[10] ^= R(x[ 6]+x[ 2],18);
            x[ 3] ^= R(x[15]+x[11], 7);  x[ 7] ^= R(x[ 3]+x[15], 9);
            x[11] ^= R(x[ 7]+x[ 3],13);  x[15] ^= R(x[11]+x[ 7],18);
            x[ 1] ^= R(x[ 0]+x[ 3], 7);  x[ 2] ^= R(x[ 1]+x[ 0], 9);
            x[ 3] ^= R(x[ 2]+x[ 1],13);  x[ 0] ^= R(x[ 3]+x[ 2],18);
            x[ 6] ^= R(x[ 5]+x[ 4], 7);  x[ 7] ^= R(x[ 6]+x[ 5], 9);
            x[ 4] ^= R(x[ 7]+x[ 6],13);  x[ 5] ^= R(x[ 4]+x[ 7],18);
            x[11] ^= R(x[10]+x[ 9], 7);  x[ 8] ^= R(x[11]+x[10], 9);
            x[ 9] ^= R(x[ 8]+x[11],13);  x[10] ^= R(x[ 9]+x[ 8],18);
            x[12] ^= R(x[15]+x[14], 7);  x[13] ^= R(x[12]+x[15], 9);
            x[14] ^= R(x[13]+x[12],13);  x[15] ^= R(x[14]+x[13],18);
        }

        for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i];

        for (i = 0; i < 16; i++) {
            B[i * 4 + 0] = (byte) (B32[i] >> 0  & 0xff);
            B[i * 4 + 1] = (byte) (B32[i] >> 8  & 0xff);
            B[i * 4 + 2] = (byte) (B32[i] >> 16 & 0xff);
            B[i * 4 + 3] = (byte) (B32[i] >> 24 & 0xff);
        }
    }

    public static void blockxor(byte[] S, int Si, byte[] D, int Di, int len) {
        for (int i = 0; i < len; i++) {
            D[Di + i] ^= S[Si + i];
        }
    }

    public static int integerify(byte[] B, int Bi, int r) {
        int n;

        Bi += (2 * r - 1) * 64;

        n  = (B[Bi + 0] & 0xff) << 0;
        n |= (B[Bi + 1] & 0xff) << 8;
        n |= (B[Bi + 2] & 0xff) << 16;
        n |= (B[Bi + 3] & 0xff) << 24;

        return n;
    }
}

 

你可能感兴趣的:(算法)