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
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
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)
核心方法 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);
}
使用例子
/**
* 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;
}
int recoverId = getRecoveryId(r, s, Sha256Hash.of(signableTransaction), keyData,
algorithmEmployed);
if (recoverId < 0) {
throw new IllegalStateException(
ErrorConstants.COULD_NOT_RECOVER_PUBLIC_KEY_FROM_SIG);
}
其他库
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
钱包App,浏览器钱包扩展Dapp签名兼容Scatter流程简单介绍
前景
最近有小伙伴要做App钱包Dapp运行相关的,以及浏览器扩展,所以做点简单的介绍
由于Scatter发展较早,以及各方钱包的大力支持,Scatter已经是Dapp运行的标准了,所以这块就是兼容(伪装)Scatter协议。
原理
对于Scatter的协议的支持,就是让三方使用Scatter.js的网页,能够无缝的运行起来(需要签名时,对其数据正确签名)
对于Scatter.js来说,支持浏览器扩展和ScatterDesktop(ws)两个版本,各种原因,Scatter浏览器扩展官方已经明确废弃,不再更新了,且引导到桌面版本。但对于App来说,我们希望的是当前的App直接使用嵌入的WebView直接打开三方的Dapp,并不需要与三方的通信或者ws(三方跳转可以参考UAL for EOSIO)
类似UAL的方式,可以把图中EOSJS改成Scatter.js

我们先以App端为例,对于一个没有接触过EOS这块的同学,我们把测试工作细分下,尽量避免区块链相关枯燥的知识。
熟悉扩展交互流程,验证Demo Dapp运行正常
- 第一步,去测试网申请测试账号,以及申请测试币,(可以请相关同学帮助)(点击跳转)
- 第二步,准备一个Chrome浏览器,安装好Scatter的扩展,(进入教程)
- 第三步,先编译一个demo Dapp,方便我们后面测试 (进入教程)
需要修改下RPC地址和端口,以及chain id。
host: http://api.kylin.eosbeijing.one
port: 80
chain_id: 5fff1dae8dc8e2fc4d5b23b2c7665c97f9e9d8edf2b6485a86ba311c25639191
此时可以点击
测试Scatter扩展版本与Demo Dapp交互正确。
伪装Scatter
上面保证了Demo Dapp与 Scatter扩展的交互正确了,但我们需要的接管扩展。也就是我们要伪装一个Scatter对象,
参考项目 (Scatter javascript warpper for webview)
简单来说就是我们在App中的WebView中注入一个js,并创建一个Scatter对象,并撒谎告诉Demo Dapp Scatter的浏览器扩展已经安装好了
inyIdentitys.initEOS("\(account)", "\(publicKey)");
const scatter = new TinyScatter();
scatter.loadPlugin(new TinyEOS());
window.scatter = scatter;
document.dispatchEvent(new CustomEvent('scatterLoaded'));
此时Demo Dapp会去当前的Content页面中找到我们伪装的Scatter对象,并与伪装Scatter接口交互数据。
我们伪装的Scatter收到数据后,把传入的数据发给WebView外边的钱包App,
比如交易签名,Demo Dapp会把需要签名的数据塞给伪装的Scatter的接口requestSignature,然后伪装的Scatter会把这些数据,暗度陈仓给Webview外边的钱包App,App选用对应的私钥签名后,将数据返回给伪装的Scatter,并返回Demo Dapp。Demo Dapp拿到签名后的数据后,继续后续的操作。
测试
当WebView打开Demo Dapp,点击
,会伪装的Scatter会拦截相关请求,比如签名,当Scatter把需要签名的数据返给外边的App,App用对应的私钥签名后,返回给伪装Scatter,能够完成与 Scatter扩展版本与Demo Dapp一样的交互.
目前已接管的接口,部分功能直接忽略
- requestSignature
- requestArbitrarySignature
- getOrRequestIdentity
- identityFromPermissions
- authenticate
- forgetIdentity
- requestAddNetwork
- getVersion
- getPublicKey
- linkAccount
- hasAccountFor
- requestTransfer
- createTransaction
其他支持方式
UAL for EOSIO Reference Authenticator
参考
https://github.com/xuewuli/Tiny.Scatter
https://github.com/EOSIO/eosio-reference-chrome-extension-authenticator-app
https://github.com/EOSIO/ual-eosio-reference-authenticator