如何在 Dart 中使用 secp256k1 创建签名,就像在 JavaScript 代码中一样

问题描述 投票:0回答:1

我正在尝试使用

secp256k1
在 Dart 中创建签名。但与 javascript 相比,我生成了不同的签名。 JavaScript 方法如下:

import * as secp from "@noble/secp256k1";    
const signature = await secp.sign(msgHash, hashingPrivateKey, {
        der: false,
        recovered: true,
    });

我需要在我的 Dart 代码中传递这些

der
recovered
参数。我无法找到任何选项来在我的 Dart 代码中添加这些参数。我的代码如下:

import 'package:secp256k1/secp256k1.dart' as secp;
    var pk = secp.PrivateKey.fromHex(hashingPrivateKey);
    var pub = pk.publicKey;
    final signature = pk.signature(msgHash);

每次我在 dart 中运行这个函数时,都会返回不同的signature.R和不同的signature.S。你能解释一下吗?

Javascript 库版本是

"@noble/secp256k1": "^1.7.0",
输入值为

msgHash : fea272bf4112f825697cfebe6f8f7dd8de1726d0c62ab45b78de4cb62f99a8dc

hashingPrivateKey : bf751b9f4cacc36548c56a88dc47aa87b93b415705b9a62aa0a669d98381f619

JavaScript 的结果签名是

A2Mt3EHNy1HHgBwt46PSUBJG0BdyPs326hoixd6/AJdaY9axCXwEQRHgt4Xb1dr9LLr5r+C/fRM24ySeioOBWAA=

dart 的结果签名是

D+ZSaLNGbFiAinyJ/B5GcLwI7dl1NqyHDkKG34iUXvlUhQ7Q6LF2gR94Nbyx2JZ3TH0fVZ4mU4MGAyWxtEDJeA==

生成自

var sig = Uint8List.fromList([
        ...NanoHelpers.bigIntToBytes(signature.R),
        ...NanoHelpers.bigIntToBytes(signature.S)
      ]);

使用

pointycaslte
我尝试了下面的函数,但也无法生成相同的签名。

ECSignature createSignature(String msgHash, String hashingPrivateKey) {
  var domain = ECDomainParameters('secp256k1');
  ECPrivateKey ecPrivateKey = ECPrivateKey(
      NanoHelpers.byteToBigInt(
          Uint8List.fromList(hex.decode(hashingPrivateKey))),
      domain);

  ECSignature signature = CryptoUtils.ecSign(
      ecPrivateKey, Uint8List.fromList(hex.decode(msgHash)),
      algorithmName: 'SHA-256/DET-ECDSA');

  var g = ecPrivateKey.parameters?.G;
  var ecPublicKey = ECPublicKey(
    g! *
        NanoHelpers.byteToBigInt(
            Uint8List.fromList(hex.decode(hashingPrivateKey))),
    domain,
  );
  var verify = CryptoUtils.ecVerify(
      ecPublicKey, Uint8List.fromList(hex.decode(msgHash)), signature,
      algorithm: 'SHA-256/DET-ECDSA');

  print('verify====$verify');
  print('ECSignature.R===${signature.r.toRadixString(16)}');
  print('ECSignature.S===${signature.s.toRadixString(16)}');
  var sig = Uint8List.fromList([
    ...NanoHelpers.bigIntToBytes(signature.r),
    ...NanoHelpers.bigIntToBytes(signature.s)
  ]);
  print('sig=createSignature==1==${base64.encode(sig)}');
  print('sig==createSignature=1==${base64.encode(sig).length}');
  return signature;
}
javascript dart cryptography ecdsa ecdsasignature
1个回答
0
投票

最终能够通过使用recoveryId和DeterministicSignature方法创建自定义签名来解决它。代码如下。

import 'package:crypto/crypto.dart';
import 'package:elliptic/elliptic.dart';
Signature getDeterministicSignature(PrivateKey priv, List<int> hash) {
  var k = generateSecret(priv.curve.n, priv.D, hash);
  var inv = k.modInverse(priv.curve.n);
  var hexK = k.toRadixString(16).padLeft((k.bitLength + 7) ~/ 8 * 2, '0');
  var p = priv.curve.scalarBaseMul(List<int>.generate(hexK.length ~/ 2,
      (i) => int.parse(hexK.substring(i * 2, i * 2 + 2), radix: 16)));
  var r = p.X % priv.curve.n;
  if (r.sign == 0) {
    throw Exception('calculated R is zero');
  }

  var e = bitsToInt(hash, priv.curve.n.bitLength);
  var s = priv.D * r + e;
  s = (s * inv) % priv.curve.n;

  if (s > (priv.curve.n >> 1)) {
    s = priv.curve.n - s;
  }

  if (s.sign == 0) {
    throw Exception('calculated S is zero');
  }

  var recoveryId = (p.Y.isOdd ? 1 : 0) | (s.isOdd ? 2 : 0);
  return Signature.fromRS(r, s, recoveryId);
}

BigInt generateSecret(BigInt q, BigInt x, List<int> hash) {
  var hasher = sha256;

  var qLen = q.bitLength;
  var hoLen =
      32; // = sha256.size, because the sha256 is fixed here so do the len
  var roLen = (qLen + 7) >> 3;

  var bx = intToOctets(x, roLen) + bitsToOctets(hash, q, roLen);
  var v = List<int>.filled(hoLen, 0x01);
  var k = List<int>.filled(hoLen, 0x00);

  k = Hmac(hasher, k).convert(v + [0x00] + bx).bytes;
  v = Hmac(hasher, k).convert(v).bytes;
  k = Hmac(hasher, k).convert(v + [0x01] + bx).bytes;
  v = Hmac(hasher, k).convert(v).bytes;

  while (true) {
    var t = <int>[];
    while (t.length * 8 < qLen) {
      v = Hmac(hasher, k).convert(v).bytes;
      t = t + v;
    }

    var secret = bitsToInt(t, qLen);
    if (secret >= BigInt.one && secret < q) {
      return secret;
    }

    k = Hmac(hasher, k).convert(v + [0x00]).bytes;
    v = Hmac(hasher, k).convert(v).bytes;
  }
}

///utils
BigInt bitsToInt(List<int> hash, int qBitLen) {
  var orderBytes = (qBitLen + 7) ~/ 8;
  if (hash.length > qBitLen) {
    hash = hash.sublist(0, orderBytes);
  }

  var ret = BigInt.parse(
      List<String>.generate(
          hash.length, (i) => hash[i].toRadixString(16).padLeft(2, '0')).join(),
      radix: 16);
  var excess = hash.length * 8 - qBitLen;
  if (excess > 0) {
    ret >> excess;
  }
  return ret;
}

List<int> intToOctets(BigInt v, int roLen) {
  var vLen = (v.bitLength + 7) ~/ 8;
  var vHex = v.toRadixString(16).padLeft(vLen * 2, '0');

  var vBytes = List<int>.generate(
      vLen, (i) => int.parse(vHex.substring(2 * i, 2 * i + 2), radix: 16));
  if (vLen < roLen) {
    vBytes = List.filled(roLen - vLen, 0) + vBytes;
  }
  if (vLen > roLen) {
    vBytes = vBytes.sublist(vLen - roLen);
  }

  return vBytes;
}

List<int> bitsToOctets(List<int> input, BigInt q, int roLen) {
  var z1 = bitsToInt(input, q.bitLength);
  var z2 = z1 - q;
  if (z2.sign < 0) {
    return intToOctets(z1, roLen);
  }
  return intToOctets(z2, roLen);
}

自定义签名类如下。

import 'package:ninja_asn1/ninja_asn1.dart';


class Signature {
  late BigInt R;
  late BigInt S;
  late int recoveryId;

  Signature.fromRS(this.R, this.S, this.recoveryId);

  Signature.fromCompact(List<int> compactBytes) {
    R = BigInt.parse(
        List<String>.generate(
                32, (i) => compactBytes[i].toRadixString(16).padLeft(2, '0'))
            .join(),
        radix: 16);
    S = BigInt.parse(
        List<String>.generate(32,
                (i) => compactBytes[i + 32].toRadixString(16).padLeft(2, '0'))
            .join(),
        radix: 16);
  }

  Signature.fromCompactHex(String compactHex) {
    R = BigInt.parse(compactHex.substring(0, 64), radix: 16);
    S = BigInt.parse(compactHex.substring(64, 128), radix: 16);
  }

  /// parsing the ECDSA signatures with the more strict
  /// Distinguished Encoding Rules (DER) of ISO/IEC 8825-1
  Signature.fromASN1(List<int> asn1Bytes) {
    _parseASN1(asn1Bytes);
  }

  /// [fromDER] is same to [fromASN1]
  /// parsing the ECDSA signatures with the more strict
  /// Distinguished Encoding Rules (DER) of ISO/IEC 8825-1
  Signature.fromDER(List<int> asn1Bytes) {
    _parseASN1(asn1Bytes);
  }

  /// parsing the ECDSA signatures with the more strict
  /// Distinguished Encoding Rules (DER) of ISO/IEC 8825-1
  Signature.fromASN1Hex(String asn1Hex) {
    _parseASN1Hex(asn1Hex);
  }

  /// [fromDERHex] is same to [fromASN1Hex]
  /// parsing the ECDSA signatures with the more strict
  /// Distinguished Encoding Rules (DER) of ISO/IEC 8825-1
  Signature.fromDERHex(String asn1Hex) {
    _parseASN1Hex(asn1Hex);
  }

  List<int> toCompact() {
    var hex = toCompactHex();
    return List<int>.generate(
        64, (i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16));
  }

  List<int> toASN1() {
    return ASN1Sequence([ASN1Integer(R), ASN1Integer(S)]).encode();
  }

  /// [toDER] equals to [toASN1],
  /// serializing the ECDSA signatures with the more strict
  /// Distinguished Encoding Rules (DER) of ISO/IEC 8825-1
  List<int> toDER() {
    return toASN1();
  }

  String toCompactHex() {
    return R.toRadixString(16).padLeft(64, '0') +
        S.toRadixString(16).padLeft(64, '0');
  }

  String toASN1Hex() {
    var asn1 = toASN1();
    return List<String>.generate(
        asn1.length, (i) => asn1[i].toRadixString(16).padLeft(2, '0')).join();
  }

  /// [toDERHex] equals to [toASN1Hex]
  String toDERHex() {
    return toASN1Hex();
  }

  /// [toString] equals to [toASN1Hex] or [toDERHex],
  /// because the ASN1 is recommended in paper
  @override
  String toString() {
    return toASN1Hex();
  }

  void _parseASN1(List<int> asn1Bytes) {
    var p = ASN1Sequence.decode(asn1Bytes);
    R = (p.children[0] as ASN1Integer).value;
    S = (p.children[1] as ASN1Integer).value;
  }

  void _parseASN1Hex(String asn1Hex) {
    var asn1Bytes = List<int>.generate(asn1Hex.length ~/ 2,
        (i) => int.parse(asn1Hex.substring(i * 2, i * 2 + 2), radix: 16));
    var p = ASN1Sequence.decode(asn1Bytes);
    R = (p.children[0] as ASN1Integer).value;
    S = (p.children[1] as ASN1Integer).value;
  }
}

最后

import 'package:elliptic/elliptic.dart' as ellep;
var sign = getDeterministicSignature(
      ellep.PrivateKey.fromHex(ellep.getSecp256k1(), hashingPrivateKey),
      hex.decode(msgHash));

效果很好。谢谢。

© www.soinside.com 2019 - 2024. All rights reserved.