您正在查看: Surou 发布的文章

EOS 签名 ECDSA sign 椭圆曲线数字签名

先零散的记一下,后面再整理

js ecc

https://github.com/EOSIO/eosjs-ecc/blob/7ec577cad54e17da6168fdfb11ec2b09d6f0e7f0/src/signature.js

sign

https://github.com/EOSIO/eosjs-ecc/blob/7ec577cad54e17da6168fdfb11ec2b09d6f0e7f0/src/signature.js#L177

Signature.sign = function(data, privateKey, encoding = 'utf8') {
    if(typeof data === 'string') {
        data = Buffer.from(data, encoding)
    }
    assert(Buffer.isBuffer(data), 'data is a required String or Buffer')
    data = hash.sha256(data)
    return Signature.signHash(data, privateKey)
}

https://github.com/EOSIO/eosjs-ecc/blob/7ec577cad54e17da6168fdfb11ec2b09d6f0e7f0/src/signature.js#L210

 ecsignature = ecdsa.sign(curve, dataSha256, privateKey.d, nonce++);

java

https://github.com/adyliu/jeos/blob/53dbd027cd59d367d9a197cbff5a58bdd9bf7195/src/main/java/io/jafka/jeos/impl/LocalApiImpl.java#L68

private String sign(String privateKey, SignArg arg, PackedTransaction t) {
    Raw raw = Packer.packPackedTransaction(arg.getChainId(), t);
    raw.pack(ByteBuffer.allocate(33).array());// TODO: what's this?
    String hash = KeyUtil.signHash(privateKey, raw.bytes());
    return hash;
}

https://github.com/adyliu/jeos/blob/4eaa9fb4555129f3d0baa56e7ffb70cac213a009/src/main/java/io/jafka/jeos/util/ecc/Ecdsa.java

https://github.com/starkbank/ecdsa-java/blob/master/src/main/java/com/starkbank/ellipticcurve/Ecdsa.java

参考

ECDSA — The art of cryptographic signatures
https://medium.com/coinmonks/ecdsa-the-art-of-cryptographic-signatures-d0bb254c8b96

Goodbye, Scatter Extension

仅作测试使用,无法保证后面不更新是否导致安全等问题,非测试建议使用官方桌面版本

今天打开Scatter浏览器扩展版本发现

Goodbye, Scatter Extension
This extension is very old, and we at Scatter no longer maintain it which makes it unsafe to use. 

Because of this we are disabling it until it gets some love. 

已经禁止使用了,力推桌面版本。
但是开发测试的需要,还是需要扩展版本。

查看ScatterWebExtension源代码提交记录

disabling extension

https://github.com/GetScatter/ScatterWebExtension/commit/c59a253abfad0318a6befe87bf9eefab5fbcbb7f
看到是新提交了禁用的代码,那我们fork一下,revert掉这次提交
https://github.com/cppfuns/ScatterWebExtension
重新打包一份

npm i
cp .env.example .env
npm run build

最后生成在 ScatterWebExtension\build

chrome 打开扩展chrome://extensions/
打开开发者模式,然后加载已解压的扩展即可

或者直接下载我这边已打包的zip,解压加载
https://github.com/cppfuns/ScatterWebExtension/raw/master/scatter.zip

注意

由于扩展版本之后不在更新,安全性等问题,还是建议非特殊原因,还是使用官方的桌面版本。

EOS的一些开源侧链

https://github.com/yottachain/YTBP
持续更新

EOS 签名中获取公钥 recover,验证登录,token

ecc

https://github.com/EOSIO/eosjs-ecc#recover

recover

Recover the public key used to create the signature.

Parameters

  • signature (String | Buffer) (EOSbase58sig.., Hex, Buffer)
  • data (String | Buffer) full data
  • encoding String data encoding (if data is a string) (optional, default 'utf8')

Examples

ecc.recover(signature, 'I am alive') === pubkey
Returns pubkey

Java

https://github.com/eosforce/eosforce-monitor/blob/9b35fcb3db0ae246b56de83485bad99502f8f5cd/src/main/java/client/SignedTransactionTest.java#L120

public void signTransactionNew_matchSignature(){
        SignedTransaction txn =createTxn( "eos", "newaccount", CREATE_ACCOUNT_DATA_AS_HEX,
                new String[]{ "inita", "eos"}, new String[]{"inita@active"});

        String head_block_id = "000009907e54bb84c7dc993e613e237f65dfbbc0a26f501b2ac7e1eb7b570df3";
        txn.setReferenceBlock(head_block_id);
        txn.setExpiration("2017-09-22T09:04:25");

        EosPrivateKey key = new EosPrivateKey("5KiA2RDrwb9xq2j6Z3k8qaz58HVhwh7mAxjWPag9dwjpBFNCGYp");

        assertEquals( "key parse failed-1",  "EOS6H6WZR2Nme3Sp4F8Krkdn19EYsTZLEyD8KasQYfa2EcqpZMohV", key.getPublicKey().toString());

        key = new EosPrivateKey("5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3");
        assertEquals( "key parse failed-2",  "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", key.getPublicKey().toString());

        byte[] data = HexUtils.toBytes("369a790be1b3192fa6eccd0e8e90b39692145d30c75eda7e435df083e30801a3");
        BigInteger r = new BigInteger(HexUtils.toBytes("545c106dfd35900fab318cc12e208140abba086ab1112c543a808e2248ddb62d"));
        BigInteger s = new BigInteger(HexUtils.toBytes("5fe909c18582e792116e418e5491d18a5c98be34e5bdccb577d6fa59806e6a28"));
        CurveParam curveParamK1 = EcTools.getCurveParam( CurveParam.SECP256_K1);
        EcSignature signature = new EcSignature( r,s, curveParamK1,1);
        assertEquals("failed to recover pubKey from sig, (recId 1)", key.getPublicKey(), EcDsa.recoverPubKey(curveParamK1,data, signature, 1) );

        r = new BigInteger(HexUtils.toBytes("40361c656f284cd676d920108d3a63dfcf0779d0296cdb2f9421c0c1fd18244a"));
        s = new BigInteger(HexUtils.toBytes("284db40e8661d75067d45b6559266ba5345e86df0af83343951284121dddd1ec"));
        signature = new EcSignature( r,s, curveParamK1,0);
        assertEquals("failed to recover pubKey from sig, (recId 0)", key.getPublicKey(), EcDsa.recoverPubKey(curveParamK1, data, signature, 0) );
    }

    public void recoverPublic(){
        byte[] data = HexUtils.toBytes("330fbd64ae1b11d2b29d74a1dfa7ca5c0ba184b835e8643fdf362365dd584b0b");
        EcSignature ecSignature = new EcSignature("SIG_K1_JuSQ5GZfvU2w1i16qxYdRQ1tdNzYtP1AwJZRcwJ4viUgbEXR9K5rMdy5bpq7gydMjPxqQqZbmVtcFEDdMY9NBHGehmNdiZ");
        EosPublicKey eosPublicKey =  EcDsa.recoverPubKey(data, ecSignature);
        logger.info("publicKey: {}", eosPublicKey.toString());

    }

其他参考

Java

https://github.com/adyliu/jeos/blob/4eaa9fb4555129f3d0baa56e7ffb70cac213a009/src/main/java/io/jafka/jeos/util/ecc/Ecdsa.java#L188

public int calcPubKeyRecoveryParam(BigInteger e, SignBigInt sign, Point Q) {
        for (int i = 0; i < 4; i++) {
            Point Qprime = recoverPubKey(e, sign, i);
            if (Qprime.equals(Q)) {
                return i;
            }
        }
        throw new RuntimeException( "Unable to find valid recovery factor");
    }

    public Point recoverPubKey(BigInteger e, SignBigInt big, int i) {

        BigInteger n = curve.n();
        Point G = curve.G();

        BigInteger r = big.getR();
        BigInteger s = big.getS();

        if (!(r.signum() > 0 && r.compareTo(n) < 0)) {
            throw new RuntimeException(  "Invalid r value");
        }
        if (!(s.signum() > 0 && s.compareTo(n) < 0)) {
            throw new RuntimeException(  "Invalid r value");
        }

        // A set LSB signifies that the y-coordinate is odd
        int isYOdd = i & 1;

        // The more significant bit specifies whether we should use the
        // first or second candidate key.
        int isSecondKey = i >> 1;

        // 1.1 Let x = r + jn
        BigInteger x = isSecondKey == 1 ? r.add(n) : r;

        Point R = curve.getCurve().pointFromX(isYOdd, x);

        // // 1.4 Check that nR is at infinity
        Point nR = R.multiply(n);

        if (!nR.isInfinity()) {
            throw new RuntimeException(  "nR is not a valid curve point");
        }

        BigInteger eNeg = e.negate().mod(n);

        BigInteger rInv = r.modInverse(n);

        Point Q = R.multiplyTwo(s, G, eNeg).multiply(rInv);

        if (Q.isInfinity()) {
            throw new RuntimeException(  "Point is at infinity");
        }

        return Q;
    }

java (适用于Swift和Java的Native SDK)

https://github.com/EOSIO/eosio-java/blob/a9202879f31edb4122e768df0dc7fca391eff7e7/eosiojava/src/main/java/one/block/eosiojava/utilities/EOSFormatter.java#L1431:27

核心方法 recoverPublicKeyFromSignature

 private static byte[] recoverPublicKeyFromSignature(int recId, BigInteger r, BigInteger s,
            @NotNull Sha256Hash message, boolean compressed, AlgorithmEmployed keyType) {
        checkArgument(recId >= 0, "recId must be positive");
        checkArgument(r.signum() >= 0, "r must be positive");
        checkArgument(s.signum() >= 0, "s must be positive");

        // 1.0 For j from 0 to h   (h == recId here and the loop is outside this function)
        //   1.1 Let x = r + jn

        BigInteger n; // Curve order.
        ECPoint g;
        ECCurve.Fp curve;

        switch (keyType) {
            case SECP256R1:
                n = ecParamsR1.getN();
                g = ecParamsR1.getG();
                curve = (ECCurve.Fp) ecParamsR1.getCurve();
                break;

            default:
                n = ecParamsK1.getN();
                g = ecParamsK1.getG();
                curve = (ECCurve.Fp) ecParamsK1.getCurve();
                break;
        }

        BigInteger i = BigInteger.valueOf((long) recId / 2);
        BigInteger x = r.add(i.multiply(n));

        //   1.2. Convert the integer x to an octet string X of length mlen using the conversion routine
        //        specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉.
        //   1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the
        //        conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then
        //        do another iteration of Step 1.
        //
        // More concisely, what these points mean is to use X as a compressed public key.
        BigInteger prime = curve.getQ();
        if (x.compareTo(prime) >= 0) {
            // Cannot have point co-ordinates larger than this as everything takes place modulo Q.
            return null;
        }
        // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities.
        // So it's encoded in the recId.
        ECPoint R = decompressKey(x, (recId & 1) == 1, keyType);
        //   1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility).
        if (!R.multiply(n).isInfinity()) {
            return null;
        }
        //   1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
        BigInteger e = message.toBigInteger();
        //   1.6. For k from 1 to 2 do the following.   (loop is outside this function via iterating recId)
        //   1.6.1. Compute a candidate public key as:
        //               Q = mi(r) * (sR - eG)
        //
        // Where mi(x) is the modular multiplicative inverse. We transform this into the following:
        //               Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
        // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation
        // ** is point multiplication and + is point addition (the EC group operator).
        //
        // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive
        // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8.
        BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
        BigInteger rInv = r.modInverse(n);
        BigInteger srInv = rInv.multiply(s).mod(n);
        BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
        ECPoint q = ECAlgorithms.sumOfTwoMultiplies(g, eInvrInv, R, srInv);
        return q.getEncoded(compressed);
    }

使用例子

https://github.com/EOSIO/eosio-java/blob/a9202879f31edb4122e768df0dc7fca391eff7e7/eosiojava/src/main/java/one/block/eosiojava/utilities/EOSFormatter.java#L1382:24

/**
     * Getting recovery id from R and S
     *
     * @param r - R in DER of Signature
     * @param s - S in DER of Signature
     * @param sha256HashMessage - Sha256Hash of signed message
     * @param publicKey - public key to validate
     * @param keyType - key type
     * @return - Recovery id of the signature. From 0 to 3. Return -1 if find nothing.
     */
    private static int getRecoveryId(BigInteger r, BigInteger s, Sha256Hash sha256HashMessage,
            byte[] publicKey, AlgorithmEmployed keyType) {
        for (int i = 0; i < NUMBER_OF_POSSIBLE_PUBLIC_KEYS; i++) {
            byte[] recoveredPublicKey = recoverPublicKeyFromSignature(i, r, s, sha256HashMessage,
                    true, keyType);

            if (Arrays.equals(publicKey, recoveredPublicKey)) {
                return i;
            }
        }

        return -1;
    }

https://github.com/EOSIO/eosio-java/blob/a9202879f31edb4122e768df0dc7fca391eff7e7/eosiojava/src/main/java/one/block/eosiojava/utilities/EOSFormatter.java#L468

int recoverId = getRecoveryId(r, s, Sha256Hash.of(signableTransaction), keyData,
                    algorithmEmployed);

if (recoverId < 0) {
    throw new IllegalStateException(
        ErrorConstants.COULD_NOT_RECOVER_PUBLIC_KEY_FROM_SIG);
}

其他库

https://github.com/PegaSysEng/pantheon/blob/6e77605ab9093c8a6772aa6910e3d1846b926a1a/crypto/src/test/java/tech/pegasys/pantheon/crypto/SECP256K1Test.java#L195

public void recoverPublicKeyFromSignature() {
    final SECP256K1.PrivateKey privateKey =
        SECP256K1.PrivateKey.create(
            new BigInteger("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", 16));
    final SECP256K1.KeyPair keyPair = SECP256K1.KeyPair.create(privateKey);

    final BytesValue data =
        BytesValue.wrap("This is an example of a signed message.".getBytes(UTF_8));
    final Bytes32 dataHash = keccak256(data);
    final SECP256K1.Signature signature = SECP256K1.sign(dataHash, keyPair);

    final SECP256K1.PublicKey recoveredPublicKey =
        SECP256K1.PublicKey.recoverFromSignature(dataHash, signature).get();
    assertEquals(keyPair.getPublicKey().toString(), recoveredPublicKey.toString());
  }

附加

如果想做登陆校验的话,可以提供单独的空的action方法,参数如下

  • 账户名
  • 其他参数
  • nonce (随机数 + 时间)

用户签名后发给服务端,服务端接收后根据上面方法可以从签名中获取对应的公钥,然后通过RPCget_key_accounts 或者查讯同步数据库查找当前账户,以及当前公钥对应的权限是否与要求相符。验证通过后返回中心服务器的token做后续处理。

参考

https://medium.com/eosio/eosio-software-release-native-sdks-for-swift-and-java-e6086ddd37b8
https://eosio.github.io/eosio-java/
https://github.com/EOSTribe/java-ecc