Подпись, созданная Ellipti c JS Lib , не может быть подтверждена на моем Golang сервере. Мой Golang сервер использует пакет по умолчанию crypto/ecdsa
.
Вот код генерации подписи в TypeScript.
import * as elliptic from "elliptic";
import { KEYUTIL } from "jsrsasign";
import fetch from "node-fetch";
function String2Hex(tmp) {
var str = "";
for (var i = 0; i < tmp.length; i++) {
str += tmp[i].charCodeAt(0).toString(16);
}
return str;
}
const ec = new elliptic.ec("p256");
const keyPair = KEYUTIL.generateKeypair("EC", "secp256r1");
const privKeyPEM = KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV");
const pubKeyPEM = KEYUTIL.getPEM(keyPair.pubKeyObj);
// @ts-ignore
const key = ec.keyFromPrivate(privKeyPEM);
const msg = JSON.stringify(pubKeyPEM);
const msgHex = String2Hex(msg);
const signature = key.sign(msgHex);
const body = JSON.stringify({
jsonrpc: "2.0",
method: "TEST_PKI",
params: {
publicKey: pubKeyPEM,
signature: signature.toDER()
},
id: "random-str"
});
fetch("http://localhost:8080/api/test-chain", {
method: "POST",
body,
headers: { "Content-Type": "application/json" }
})
.then(res => res.json())
.then(res => {
if (res.error) {
console.log(res.error.message);
} else {
console.log("success");
}
});
Этот код просто генерирует ключ сопрягает и генерирует подпись над простым текстом строки (точнее говоря, эта простая строка является PEM-закодированным текстом ключа publi c, но не важно, какой текст строки был использован).
Тогда , он отправляет эту подпись с ключом publi c в кодировке PEM на сервер. Сервер читает этот код PEM publi c и проверяет подпись.
А вот код сервера:
func VerifySignature(publicKey string, target interface{}, signatureBytes []byte) error {
targetBytes, err := json.Marshal(target)
if err != nil {
return errors.WithStack(err)
}
encodedTarget := hex.EncodeToString(targetBytes)
var signature struct {
R *big.Int
S *big.Int
}
_, err = asn1.Unmarshal(signatureBytes, &signature)
if err != nil {
return err
}
pubKey, err := getPubKey(publicKey)
if err != nil {
return err
}
valid := ecdsa.Verify(pubKey, []byte(encodedTarget), signature.R, signature.S)
if !valid {
return errors.New("wrong signature")
}
return nil
}
В этом коде сервер проверяет подпись, используя функция по умолчанию ecdsa.Verify
.
Я проверил, что все входные параметры одинаковы как на стороне клиента, так и на стороне сервера. Итак, я уверен, что при отправке запроса произошла ошибка. Значения подписи R
и S
и значение encodedTarget
хорошо поступают на сервер. Однако сервер продолжает возвращать false
для проверки. (Конечно, я уже проверил функцию verify
библиотеки Ellipti c, которая возвращает true
, как и предполагалось)
Кроме того, я проверил построчно обе функции verify
в Ellipti c * Пакет 1069 * lib и crypto/ecdsa
.
Вот функция verify
библиотеки Ellipti c:
EC.prototype.verify = function verify(msg, signature, key, enc) {
msg = this._truncateToN(new BN(msg, 16));
key = this.keyFromPublic(key, enc);
signature = new Signature(signature, 'hex');
// Perform primitive values validation
var r = signature.r;
var s = signature.s;
if (r.cmpn(1) < 0 || r.cmp(this.n) >= 0)
return false;
if (s.cmpn(1) < 0 || s.cmp(this.n) >= 0)
return false;
// Validate signature
var sinv = s.invm(this.n);
var u1 = sinv.mul(msg).umod(this.n);
var u2 = sinv.mul(r).umod(this.n);
if (!this.curve._maxwellTrick) {
var p = this.g.mulAdd(u1, key.getPublic(), u2);
if (p.isInfinity())
return false;
return p.getX().umod(this.n).cmp(r) === 0;
}
// NOTE: Greg Maxwell's trick, inspired by:
// https://git.io/vad3K
var p = this.g.jmulAdd(u1, key.getPublic(), u2);
if (p.isInfinity())
return false;
// Compare `p.x` of Jacobian point with `r`,
// this will do `p.x == r * p.z^2` instead of multiplying `p.x` by the
// inverse of `p.z^2`
return p.eqXToP(r);
};
А вот Verify
функция crypto/ecdsa
:
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
// See [NSA] 3.4.2
c := pub.Curve
N := c.Params().N
if r.Sign() <= 0 || s.Sign() <= 0 {
return false
}
if r.Cmp(N) >= 0 || s.Cmp(N) >= 0 {
return false
}
e := hashToInt(hash, c)
return verify(pub, c, e, r, s)
}
// Verify calls verifyGeneric
func verifyGeneric(pub *PublicKey, c elliptic.Curve, e, r, s *big.Int) bool {
var w *big.Int
N := c.Params().N
if in, ok := c.(invertible); ok {
w = in.Inverse(s)
} else {
w = new(big.Int).ModInverse(s, N)
}
u1 := e.Mul(e, w)
u1.Mod(u1, N)
u2 := w.Mul(r, w)
u2.Mod(u2, N)
// Check if implements S1*g + S2*p
var x, y *big.Int
if opt, ok := c.(combinedMult); ok {
x, y = opt.CombinedMult(pub.X, pub.Y, u1.Bytes(), u2.Bytes())
} else {
x1, y1 := c.ScalarBaseMult(u1.Bytes())
x2, y2 := c.ScalarMult(pub.X, pub.Y, u2.Bytes())
x, y = c.Add(x1, y1, x2, y2)
}
if x.Sign() == 0 && y.Sign() == 0 {
return false
}
x.Mod(x, N)
return x.Cmp(r) == 0
}
Я не крипто-человек, поэтому я не знаю, что означают эти переменные. Однако я печатал построчно и обнаруживал некоторые различия между ними.
Во-первых, результат this._truncateToN(new BN(msg, 16));
в Ellipti c отличается от результата hashToInt(hash, c)
из crypto/ecdsa
.
Во-вторых, var sinv = s.invm(this.n);
значение в эллиптике c отличается от w = in.Inverse(s)
из crypto/ecdsa
.
И из-за этих различий значения u1
в обоих значениях и x
в Golang (p.x
в Ellipti c) различаются. Таким образом, в конечном итоге это приводит к другому результату проверки.
Итак, как мне решить эту проблему? Как я могу исправить свой код? Должен ли я найти другую библиотеку JS? (Если тогда, какая библиотека может быть полезна?) Пожалуйста, дайте мне несколько советов. Спасибо!