Skip to content

Commit

Permalink
Add ECCompressedPublicKey class
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewLM committed Aug 30, 2024
1 parent 1eb13ba commit 5d39db9
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 8 deletions.
1 change: 1 addition & 0 deletions coinlib/lib/src/coinlib_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export 'package:coinlib/src/common/bytes.dart';
export 'package:coinlib/src/common/hex.dart';
export 'package:coinlib/src/common/serial.dart';

export 'package:coinlib/src/crypto/ec_compressed_public_key.dart';
export 'package:coinlib/src/crypto/ec_private_key.dart';
export 'package:coinlib/src/crypto/ec_public_key.dart';
export 'package:coinlib/src/crypto/ecdsa_signature.dart';
Expand Down
31 changes: 31 additions & 0 deletions coinlib/lib/src/crypto/ec_compressed_public_key.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'dart:typed_data';

import 'package:coinlib/src/common/hex.dart';
import 'ec_public_key.dart';

/// Represents an [ECPublicKey] that must be in a compressed format, or else a
/// [InvalidPublicKey] will be thrown.
class ECCompressedPublicKey extends ECPublicKey {

ECCompressedPublicKey(super.data) {
if (data.length != 33) throw InvalidPublicKey();
}
ECCompressedPublicKey.fromHex(String hex) : this(hexToBytes(hex));
ECCompressedPublicKey.fromXOnly(super.xcoord) : super.fromXOnly();
ECCompressedPublicKey.fromXOnlyHex(super.hex) : super.fromXOnlyHex();
ECCompressedPublicKey.fromPubkey(ECPublicKey key) : this(
key.compressed
? key.data
: Uint8List.fromList([key.yIsEven ? 2 : 3, ...key.x]),
);

@override
ECCompressedPublicKey? tweak(Uint8List scalar) {
final tweaked = super.tweak(scalar);
return tweaked == null ? null : ECCompressedPublicKey.fromPubkey(tweaked);
}

@override
ECCompressedPublicKey get xonly => ECCompressedPublicKey.fromXOnly(x);

}
65 changes: 65 additions & 0 deletions coinlib/test/crypto/ec_compressed_public_key_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:coinlib/coinlib.dart';
import 'package:test/test.dart';
import '../vectors/keys.dart';

void main() {

group("ECCompressedPublicKey", () {

setUpAll(loadCoinlib);

test("requires 33 bytes", () {

for (final failing in [
// Too small
pubkeyVec.substring(0, 32*2),
// Too large
longPubkeyVec,
"${pubkeyVec}ff",
]) {
expect(
() => ECCompressedPublicKey.fromHex(failing),
throwsA(isA<InvalidPublicKey>()),
);
}

});

test("accepts compressed types", () {
for (final vec in validPubKeys) {
if (!vec.compressed) continue;
final pk = ECCompressedPublicKey.fromHex(vec.hex);
expect(pk.hex, vec.hex);
expect(pk.compressed, true);
expect(pk.yIsEven, vec.evenY);
}
});

test(".fromXOnly", () => expect(
ECCompressedPublicKey.fromXOnlyHex(xOnlyPubkeyVec).hex,
"02$xOnlyPubkeyVec",
),);

test(".fromPubkey", () {

void expectCompressedKey(String pubkey, String compressed) => expect(
ECCompressedPublicKey.fromPubkey(ECPublicKey.fromHex(pubkey)).hex,
compressed,
);

expectCompressedKey(longPubkeyVec, pubkeyVec);
expectCompressedKey(pubkeyVec, pubkeyVec);
expectCompressedKey(
"06ef164284e2c3abc32b310eb62904af0d49196c51087bdf4038998f8818787c882433ae83422904f48ad36dcf351ac9a37e6b00e57cf40b469b650ec850640efe",
"02ef164284e2c3abc32b310eb62904af0d49196c51087bdf4038998f8818787c88",
);
expectCompressedKey(
"07576168b540f6f80e4d2a325f8cbd420ceb170ff42cd07e96bffc5e6a4a4ea04b1208f618306fd629cd2972cea45aa81ae7b24a64bf2e86704d7a63d82fd97a8f",
"03576168b540f6f80e4d2a325f8cbd420ceb170ff42cd07e96bffc5e6a4a4ea04b",
);

});

});

}
14 changes: 6 additions & 8 deletions coinlib/test/crypto/ec_public_key_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ void main() {

for (final failing in [
// Too small
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
pubkeyVec.substring(0, 32*2),
longPubkeyVec.substring(0, 32*2),
// Too large
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021",
"${pubkeyVec}ff",
"${longPubkeyVec}ff",
]) {
expect(
() => ECPublicKey.fromHex(failing),
Expand Down Expand Up @@ -46,10 +46,8 @@ void main() {
test(".fromXOnly", () {

expect(
ECPublicKey.fromXOnlyHex(
"d69c3509bb99e412e68b0fe8544e72837dfa30746d8be2aa65975f29d22dc7b9",
).hex,
"02d69c3509bb99e412e68b0fe8544e72837dfa30746d8be2aa65975f29d22dc7b9",
ECCompressedPublicKey.fromXOnlyHex(xOnlyPubkeyVec).hex,
"02$xOnlyPubkeyVec",
);

for (final invalid in [
Expand Down
1 change: 1 addition & 0 deletions coinlib/test/vectors/keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class KeyTestVector {
final pubkeyVec = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
final longPubkeyVec = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
final pubkeyhashVec = "751e76e8199196d454941c45d1b3a323f1433bd6";
final xOnlyPubkeyVec = "d69c3509bb99e412e68b0fe8544e72837dfa30746d8be2aa65975f29d22dc7b9";

final keyPairVectors = [
KeyTestVector(
Expand Down

0 comments on commit 5d39db9

Please sign in to comment.