diff --git a/example/recover_public_key_example.dart b/example/recover_public_key_example.dart new file mode 100644 index 0000000..0a2a506 --- /dev/null +++ b/example/recover_public_key_example.dart @@ -0,0 +1,33 @@ +import 'dart:typed_data'; + +import 'package:witnet/crypto.dart'; +import 'package:witnet/src/crypto/hd_wallet/extended_private_key.dart'; +import 'package:witnet/src/crypto/secp256k1/secp256k1.dart'; +import 'package:witnet/src/crypto/secp256k1/signature.dart'; +import 'package:witnet/src/utils/transformations/transformations.dart'; +import 'package:witnet/witnet.dart'; + +// abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about +String xprvString = + "xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvu566tn6"; + +void main() { + // import a xprv + Xprv xprv = Xprv.fromXprv(xprvString); + var expectedKey = bytesToHex(xprv.privateKey.publicKey.point.encode()); + + // sign a message + String messageStr = "Hello Witnet!"; + Uint8List messageBytes = sha256(data: stringToBytes(messageStr)); + WitSignature signature = xprv.privateKey.signature(bytesToHex(messageBytes)); + + // recover a public key from a signature + WitPublicKey recoveredKey = WitPublicKey.recover(signature, messageBytes); + + try { + assert(expectedKey == bytesToHex(recoveredKey.point.encode()), + "error: Message not signed by expected Public Key"); + } catch (e) { + print(e); + } +} diff --git a/lib/src/crypto/secp256k1/public_key.dart b/lib/src/crypto/secp256k1/public_key.dart index 05c4a25..dcbf3c8 100644 --- a/lib/src/crypto/secp256k1/public_key.dart +++ b/lib/src/crypto/secp256k1/public_key.dart @@ -1,11 +1,13 @@ import "dart:typed_data" show Uint8List; +import 'package:witnet/src/crypto/secp256k1/signature.dart'; + import 'secp256k1.dart' - show Point, hexToPoint, hexToPointFromCompress, pointToHexInCompress; + show Point, Secp256k1, hexToPoint, hexToPointFromCompress, pointToHexInCompress; import 'private_key.dart' show WitPrivateKey; import '../crypto.dart' show sha256; -import 'package:witnet/utils.dart' show bech32, bytesToHex, hexToBytes; +import 'package:witnet/utils.dart' show bech32, bytesToBigInt, bytesToHex, hexToBytes; class WitPublicKey { final Point point; @@ -31,6 +33,25 @@ class WitPublicKey { return privateKey.publicKey; } + factory WitPublicKey.recover(WitSignature signature, Uint8List message, [int? recoveryId = null]) { + if(recoveryId != null) { + if(recoveryId >=0 && recoveryId <=3) { + return WitPublicKey(_recoverPublicKey(recoveryId, signature, message)); + } else { + throw ArgumentError("invalid Recovery ID: 0-3... $recoveryId"); + } + } else { + for (int recId = 0; recId <= 3; recId++) { + Point recoveredKey = _recoverPublicKey(recId,signature, message); + WitPublicKey publicKey = WitPublicKey(recoveredKey); + if(signature.verify(publicKey, bytesToHex(message))) { + return publicKey; + } + } + throw ArgumentError('Could not calculate recovery ID'); + } + } + Uint8List encode({bool compressed = true}) { return hexToBytes(pointToHexInCompress(point)); } @@ -47,3 +68,48 @@ class WitPublicKey { return bech32.encodeAddress('wit', publicKeyHash); } } + +Point _recoverPublicKey(int recoveryId, WitSignature signature, Uint8List message) { + BigInt z = bytesToBigInt(message); + if(signature.R >= Secp256k1.n || signature.S >= Secp256k1.n) { + throw ArgumentError("Invalid Signature"); + } + + // calculate x coordinate of point R + BigInt x = signature.R + BigInt.from(recoveryId / 2) * Secp256k1.n; + if(x >= Secp256k1.p) { + throw ArgumentError("invalid x-coordinate"); + } + + // decompress point R from the x coordinate + Point r = _decompressKey(x, (recoveryId % 2) == 1); + + BigInt e = z % Secp256k1.n; + BigInt eInv = (Secp256k1.n - e) % Secp256k1.n; + BigInt rInv = signature.R.modInverse(Secp256k1.n); + BigInt srInv = (signature.S * rInv) % Secp256k1.n; + BigInt eInvrInv = (eInv * rInv) % Secp256k1.n; + + // Q = r^-1 (sR - eG) + Point q = (r * srInv) + (Secp256k1.G * eInvrInv); + + return q; +} + +_decompressKey(BigInt xBn, bool yBit) { + var x = xBn; + + // y^2 = x^3 + ax + b (mod p) + var alpha = (x.modPow(BigInt.from(3), Secp256k1.p) + BigInt.from(7) % Secp256k1.p); + + // y = sqrt(y^2) (mod p) + var beta = (alpha.modPow((Secp256k1.p + BigInt.one) >> 2, Secp256k1.p)); + + // select the correct y based on the yBit + var y = beta; + if((beta.isEven ? 0 : 1) != (yBit ? 1 : 0)) { + y = Secp256k1.p - y; + } + + return Point(x, y); +} diff --git a/lib/src/crypto/secp256k1/secp256k1.dart b/lib/src/crypto/secp256k1/secp256k1.dart index fbfb3b1..c0acc8a 100644 --- a/lib/src/crypto/secp256k1/secp256k1.dart +++ b/lib/src/crypto/secp256k1/secp256k1.dart @@ -52,6 +52,10 @@ class Point { Point operator +(Point other) { return addDiffPoint(this, other, Secp256k1.p); } + + Point operator *(BigInt other) { + return pointMultiply(this, other, Secp256k1.p, Secp256k1.a); + } } Point bigIntToPoint(BigInt n) { @@ -93,6 +97,26 @@ Point addDiffPoint(Point point1, Point point2, BigInt modNum) { return Point(x3, y3); } +/// double-and-add method for point multiplication. +Point pointMultiply(Point point, BigInt k, BigInt modNum, BigInt a) { + Point result = Point(BigInt.zero, BigInt.zero); + Point addend = point; + + while (k > BigInt.zero) { + if (k.isOdd) { + if (result.x == BigInt.zero && result.y == BigInt.zero) { + result = addend; + } else { + result = addDiffPoint(result, addend, modNum); + } + } + addend = addSamePoint(addend, modNum, a); + k = k >> 1; // k = k / 2 + } + + return result; +} + Point getPointByBigInt(BigInt n, BigInt p, BigInt a, Point pointG) { var bin = n.toRadixString(2); var nextPoint = pointG; diff --git a/lib/src/crypto/secp256k1/signature.dart b/lib/src/crypto/secp256k1/signature.dart index 90e4928..97eaa05 100644 --- a/lib/src/crypto/secp256k1/signature.dart +++ b/lib/src/crypto/secp256k1/signature.dart @@ -75,6 +75,9 @@ class WitSignature { return WitSignature(r, s); } + WitPublicKey publicKey(Uint8List message) => + WitPublicKey.recover(this, message); + Uint8List encode() { Uint8List _r = bigIntToBytes(R); Uint8List _s = bigIntToBytes(S);