Skip to content

Commit

Permalink
feat: recover public key from signature
Browse files Browse the repository at this point in the history
  • Loading branch information
parodyBit committed May 29, 2024
1 parent 6453865 commit 6d6c2a7
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 2 deletions.
33 changes: 33 additions & 0 deletions example/recover_public_key_example.dart
Original file line number Diff line number Diff line change
@@ -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);
}
}
70 changes: 68 additions & 2 deletions lib/src/crypto/secp256k1/public_key.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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));
}
Expand All @@ -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);
}
24 changes: 24 additions & 0 deletions lib/src/crypto/secp256k1/secp256k1.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions lib/src/crypto/secp256k1/signature.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 6d6c2a7

Please sign in to comment.