Skip to content

Commit

Permalink
Merge pull request #22 from peercoin/2-taproot
Browse files Browse the repository at this point in the history
Add Taproot Support for Version 2.0 of Library
  • Loading branch information
MatthewLM committed Nov 3, 2023
2 parents e931281 + 83f0cdc commit 76aa33f
Show file tree
Hide file tree
Showing 79 changed files with 4,355 additions and 598 deletions.
5 changes: 5 additions & 0 deletions coinlib/bin/build_wasm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ RUN ${WASI_SDK_PATH}/bin/wasm-ld \
--export secp256k1_ecdsa_recover \
--export secp256k1_ec_seckey_tweak_add \
--export secp256k1_ec_pubkey_tweak_add \
--export secp256k1_ec_seckey_negate \
--export secp256k1_keypair_create \
--export secp256k1_xonly_pubkey_parse \
--export secp256k1_schnorrsig_sign32 \
--export secp256k1_schnorrsig_verify \
# The secp256k1 library object files
src/libsecp256k1_la-secp256k1.o \
src/libsecp256k1_precomputed_la-precomputed_ecmult.o \
Expand Down
2 changes: 1 addition & 1 deletion coinlib/bin/docker_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Future<String> getDockerCmd() async {
} else if (await cmdAvailable("docker")) {
return "docker";
} else {
print("Could not find podman or docker to use for wasm build");
print("Could not find podman or docker to use for build");
exit(1);
}

Expand Down
41 changes: 41 additions & 0 deletions coinlib/bin/generate_nums_point.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'dart:io';
import 'package:coinlib/coinlib.dart';

void main() async {

await loadCoinlib();

// Point as in https://www.secg.org/sec2-v2.pdf
final generatorBytes = hexToBytes(
"0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",
);
final generatorPoint = ECPublicKey(generatorBytes);

// Verify point is the same as key from private key = 1
final onePrivKey = ECPrivateKey.fromHex(
"0000000000000000000000000000000000000000000000000000000000000001",
compressed: false,
);

if (generatorPoint == onePrivKey.pubkey) {
print("Generator is as expected");
} else {
print("Generator isn't as expected");
exit(0);
}

// Take SHA-256 of uncompressed generator bytes
final numsX = sha256Hash(generatorBytes);
final numsXHex = bytesToHex(numsX);

// Check against expected from BIP0341
final expectedXHex
= "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";

if (numsXHex == expectedXHex) {
print("NUMS Point X-Coordinate HEX: $numsXHex");
} else {
print("NUMS point isn't as expected");
}

}
34 changes: 33 additions & 1 deletion coinlib/example/coinlib_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ void main() async {

// Create a transaction that spends a P2PKH input to the address generated
// earlier. The version is set to 3 by default with a 0 locktime.
// hexToBytes is a convenience function.

print("\nP2PKH transaction");

// hexToBytes is a convenience function.
final prevHash = hexToBytes(
"32d1f1cf811456c6da4ef9e1cb7f8bb80c4c5e9f2d2c3d743f2b68a9c6857823",
);
Expand Down Expand Up @@ -83,4 +85,34 @@ void main() async {
print("Txid = ${signedTx.txid}");
print("Tx hex = ${signedTx.toHex()}");

print("\nTaproot");

// Create a Taproot object with an internal key
final taproot = Taproot(internalKey: key1.publicKey);

// Print P2TR address
final trAddr = P2TRAddress.fromTaproot(
taproot, hrp: NetworkParams.mainnet.bech32Hrp,
);
print("Taproot address: $trAddr");

// Sign a TR input using key-path. Send to an identical TR output

final trOutput = Output.fromProgram(
BigInt.from(123456),
P2TR.fromTaproot(taproot),
);

final trTx = Transaction(
inputs: [TaprootKeyInput(prevOut: OutPoint(prevHash, 1))],
outputs: [trOutput],
).sign(
inputN: 0,
// Private keys must be tweaked by the Taproot object
key: taproot.tweakPrivateKey(key1.privateKey),
prevOuts: [trOutput],
);

print("TR Tx hex = ${trTx.toHex()}");

}
30 changes: 27 additions & 3 deletions coinlib/lib/src/address.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import 'package:coinlib/src/encode/bech32.dart';
import 'package:coinlib/src/scripts/program.dart';
import 'package:coinlib/src/scripts/programs/p2pkh.dart';
import 'package:coinlib/src/scripts/programs/p2sh.dart';
import 'package:coinlib/src/scripts/programs/p2tr.dart';
import 'package:coinlib/src/scripts/programs/p2witness.dart';
import 'package:coinlib/src/scripts/programs/p2wpkh.dart';
import 'package:coinlib/src/scripts/programs/p2wsh.dart';
import 'package:coinlib/src/scripts/script.dart';
import 'package:coinlib/src/taproot.dart';

class InvalidAddress implements Exception {}
class InvalidAddressNetwork implements Exception {}
Expand Down Expand Up @@ -203,8 +205,15 @@ abstract class Bech32Address implements Address {
} else {
throw InvalidAddress();
}
} else if (version == 1) {
// Version 1 is Taproot
if (bytes.length == 32) {
addr = P2TRAddress.fromTweakedKeyX(bytes, hrp: bech32.hrp);
} else {
throw InvalidAddress();
}
} else if (version <= 16) {
// Treat other versions as unknown. Will add version 1 taproot later
// Treat other versions as unknown.
if (bytes.length < 2 || bytes.length > maxWitnessProgramLength) {
throw InvalidAddress();
}
Expand Down Expand Up @@ -262,9 +271,24 @@ class P2WSHAddress extends Bech32Address {

}

class P2TRAddress extends Bech32Address {

P2TRAddress.fromTweakedKeyX(Uint8List tweakedKeyX, { required String hrp })
: super._(1, copyCheckBytes(tweakedKeyX, 32), hrp);

P2TRAddress.fromTweakedKey(ECPublicKey tweakedKey, { required String hrp })
: super._(1, tweakedKey.x, hrp);

P2TRAddress.fromTaproot(Taproot taproot, { required String hrp })
: super._(1, taproot.tweakedKey.x, hrp);

@override
P2TR get program => P2TR.fromTweakedKeyX(_data);

}

/// This address type is for all bech32 addresses that do not match known
/// witness versions. Currently this includes taproot until it is fully
/// specified.
/// witness versions.
class UnknownWitnessAddress extends Bech32Address {

/// Constructs a bech32 witness address from the "witness program" [data],
Expand Down
116 changes: 100 additions & 16 deletions coinlib/lib/src/bindings/secp256k1_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class SigWithRecId {

abstract class Secp256k1Base<
CtxPtr, HeapArrayPtr, PubKeyPtr, SizeTPtr, SignaturePtr,
RecoverableSignaturePtr, IntPtr, NullPtr
RecoverableSignaturePtr, KeyPairPtr, XPubKeyPtr, IntPtr, NullPtr
> {

static const contextNone = 1;
Expand All @@ -31,6 +31,8 @@ abstract class Secp256k1Base<
static const sigSize = 64;
static const derSigSize = 72;
static const recSigSize = 65;
static const keyPairSize = 96;
static const xonlySize = 32;

// Functions
late int Function(CtxPtr, HeapArrayPtr) extEcSeckeyVerify;
Expand Down Expand Up @@ -75,9 +77,20 @@ abstract class Secp256k1Base<
) extEcdsaRecover;
late int Function(CtxPtr, HeapArrayPtr, HeapArrayPtr) extEcSeckeyTweakAdd;
late int Function(CtxPtr, PubKeyPtr, HeapArrayPtr) extEcPubkeyTweakAdd;
late int Function(CtxPtr, HeapArrayPtr) extEcSeckeyNegate;

// Schnorr functions
late int Function(CtxPtr, KeyPairPtr, HeapArrayPtr) extKeypairCreate;
late int Function(CtxPtr, XPubKeyPtr, HeapArrayPtr) extXOnlyPubkeyParse;
late int Function(
CtxPtr, HeapArrayPtr, HeapArrayPtr, KeyPairPtr, HeapArrayPtr,
) extSchnorrSign32;
late int Function(
CtxPtr, HeapArrayPtr, HeapArrayPtr, int, XPubKeyPtr,
) extSchnorrVerify;

// Heap arrays
late HeapArrayBase privKeyArray;
late HeapArrayBase key32Array; // Used for private keys and x-only public keys
late HeapArrayBase scalarArray;
late HeapArrayBase serializedPubKeyArray;
late HeapArrayBase hashArray;
Expand All @@ -91,6 +104,8 @@ abstract class Secp256k1Base<
late SizeTPtr sizeTPtr;
late SignaturePtr sigPtr;
late RecoverableSignaturePtr recSigPtr;
late KeyPairPtr keyPairPtr;
late XPubKeyPtr xPubKeyPtr;
late IntPtr recIdPtr;
late NullPtr nullPtr;

Expand Down Expand Up @@ -151,6 +166,20 @@ abstract class Secp256k1Base<
}
}

void _parsePrivKeyIntoKeyPairPtr(Uint8List privKey) {
key32Array.load(privKey);
if (extKeypairCreate(ctxPtr, keyPairPtr, key32Array.ptr) != 1) {
throw Secp256k1Exception("Invalid private key");
}
}

void _parseXPubKeyIntoPtr(Uint8List pubKey) {
key32Array.load(pubKey);
if (extXOnlyPubkeyParse(ctxPtr, xPubKeyPtr, key32Array.ptr) != 1) {
throw Secp256k1Exception("Invalid x-only public key");
}
}

bool _noRaiseAfterRequireLoad(void Function() fn) {
_requireLoad();
try {
Expand Down Expand Up @@ -180,8 +209,8 @@ abstract class Secp256k1Base<
/// Returns true if a 32-byte [privKey] is valid.
bool privKeyVerify(Uint8List privKey) {
_requireLoad();
privKeyArray.load(privKey);
return extEcSeckeyVerify(ctxPtr, privKeyArray.ptr) == 1;
key32Array.load(privKey);
return extEcSeckeyVerify(ctxPtr, key32Array.ptr) == 1;
}

/// Returns true if a compressed or uncompressed public key is valid.
Expand All @@ -205,10 +234,10 @@ abstract class Secp256k1Base<
Uint8List privToPubKey(Uint8List privKey, bool compressed) {
_requireLoad();

privKeyArray.load(privKey);
key32Array.load(privKey);

// Derive public key from private key
if (extEcPubkeyCreate(ctxPtr, pubKeyPtr, privKeyArray.ptr) != 1) {
if (extEcPubkeyCreate(ctxPtr, pubKeyPtr, key32Array.ptr) != 1) {
throw Secp256k1Exception("Cannot compute public key from private key");
}

Expand Down Expand Up @@ -268,14 +297,14 @@ abstract class Secp256k1Base<
) {
_requireLoad();

privKeyArray.load(privKey);
key32Array.load(privKey);
hashArray.load(hash);
if (extraEntropy != null) entropyArray.load(extraEntropy);

// Sign
if (
extEcdsaSign(
ctxPtr, sigPtr, hashArray.ptr, privKeyArray.ptr,
ctxPtr, sigPtr, hashArray.ptr, key32Array.ptr,
// Passing null will give secp256k1_nonce_function_rfc6979. If secp256k1
// changes this default function in the future,
// secp256k1_nonce_function_rfc6979 should be used directly.
Expand All @@ -292,7 +321,7 @@ abstract class Secp256k1Base<

}

/// Verifys a compact [signature] against a 32-byte [hash] for a [pubKey] that
/// Verifies a compact [signature] against a 32-byte [hash] for a [pubKey] that
/// is either compressed or uncompressed in size
bool ecdsaVerify(Uint8List signature, Uint8List hash, Uint8List pubKey) {
_requireLoad();
Expand All @@ -308,12 +337,12 @@ abstract class Secp256k1Base<
SigWithRecId ecdsaSignRecoverable(Uint8List hash, Uint8List privKey) {
_requireLoad();

privKeyArray.load(privKey);
key32Array.load(privKey);
hashArray.load(hash);

if (
extEcdsaSignRecoverable(
ctxPtr, recSigPtr, hashArray.ptr, privKeyArray.ptr, nullPtr, nullPtr,
ctxPtr, recSigPtr, hashArray.ptr, key32Array.ptr, nullPtr, nullPtr,
) != 1
) {
throw Secp256k1Exception("Cannot sign message with private key");
Expand Down Expand Up @@ -346,20 +375,20 @@ abstract class Secp256k1Base<

}

/// Tweaks a private key ([privKey]) by a [scalar]. Returns null if a tweaked
/// private key could not be created.
/// Tweaks a 32-byte private key ([privKey]) by a [scalar]. Returns null if a
/// tweaked private key could not be created.
Uint8List? privKeyTweak(Uint8List privKey, Uint8List scalar) {
_requireLoad();

privKeyArray.load(privKey);
key32Array.load(privKey);
scalarArray.load(scalar);

if (extEcSeckeyTweakAdd(ctxPtr, privKeyArray.ptr, scalarArray.ptr) != 1) {
if (extEcSeckeyTweakAdd(ctxPtr, key32Array.ptr, scalarArray.ptr) != 1) {
return null;
}

// Return copy of private key or contents are subject to change
return Uint8List.fromList(privKeyArray.list);
return Uint8List.fromList(key32Array.list);

}

Expand All @@ -382,6 +411,61 @@ abstract class Secp256k1Base<

}

/// Takes a 32-byte private key ([privKey]) and negates it.
Uint8List privKeyNegate(Uint8List privKey) {
_requireLoad();

key32Array.load(privKey);

if (extEcSeckeyNegate(ctxPtr, key32Array.ptr) != 1) {
throw Secp256k1Exception("Invalid private key for negation");
}

return Uint8List.fromList(key32Array.list);

}

/// Constructs a 64-byte Schnorr signature for the 32-byte message [hash] and
/// [privKey] scalar. [extraEntropy] (known as auxiliary data) is optional. It
/// is recommended by secp256k1 for protection against side-channel attacks
/// but the Peercoin client does not use it and it causes signatures to lose
/// determinism.
Uint8List schnorrSign(
Uint8List hash, Uint8List privKey, [Uint8List? extraEntropy,]
) {
_requireLoad();

_parsePrivKeyIntoKeyPairPtr(privKey);
hashArray.load(hash);

if (
extSchnorrSign32(
ctxPtr, serializedSigArray.ptr, hashArray.ptr, keyPairPtr,
extraEntropy == null ? nullPtr : entropyArray.ptr,
) != 1
) {
throw Secp256k1Exception("Cannot sign Schnorr signature");
}

return serializedSigArray.list.sublist(0);

}

/// Verifies a 64-byte Schnorr [signature] against a 32-byte [hash] with a
/// 32-byte x-only public key ([xPubKey]).
bool schnorrVerify(Uint8List signature, Uint8List hash, Uint8List xPubKey) {
_requireLoad();

serializedSigArray.load(signature);
hashArray.load(hash);
_parseXPubKeyIntoPtr(xPubKey);

return extSchnorrVerify(
ctxPtr, serializedSigArray.ptr, hashArray.ptr, 32, xPubKeyPtr,
) == 1;

}

/// Specialised sub-classes should override to set the value behind the
/// sizeTPtr
set sizeT(int size);
Expand Down
Loading

0 comments on commit 76aa33f

Please sign in to comment.