Getting your Trinity Audio player ready...
|
This post was first published on Medium.
We have implemented ECDSA signature verification algorithm in Script. It can verify if an arbitrary message is signed by a private key corresponding to a given public key, while OP_CHECKSIG can only verify signatures when the message is the current spending transaction¹. Surprisingly, this is done without introducing any new opcode such as OP_DATASIGVERIFY (aka, OP_CHECKDATASIG) on BCH.
Elliptic Curve Digital Signature Algorithm (ECDSA)
ECDSA is the algorithm used in Bitcoin for signature generation and verification. The verification algorithm is listed below.

Implementation
We have implemented the algorithm as shown below, using the elliptic curve library we released before.
First, we need to extract r and s components from the signature, which is encoded in DER format. Since they are big-endian, we have to convert to little-endian, which is how data is encoded in Script/sCrypt.

After r and s are retrieved, we simply run the standard ECDSA verification algorithm.
import "ec.scrypt"; | |
import "util.scrypt"; | |
struct RSPair { | |
int r; | |
int s; | |
} | |
// ECDSA signatures verification for secp256k1, for arbitrary message @msg | |
contract ECDSA { | |
public function verify(Sig sig, PubKey pubKey, bytes msg, | |
int invS, Point P, int lambda, Point U1, PointMulAux u1Aux, Point U2, PointMulAux u2Aux) { | |
// extract (r, s) from sig | |
RSPair rs = parseDERSig(sig); | |
int r = rs.r; | |
int s = rs.s; | |
// within range | |
require(r >= 1 && r < EC.n); | |
require(s >= 1 && s < EC.n); | |
// verify invS | |
require((s * invS) % EC.n == 1); | |
int e = unpack(sha256(msg)); | |
int u1 = (e * invS) % EC.n; | |
int u2 = (r * invS) % EC.n; | |
// U1 = u1 * G | |
require(EC.isMul(EC.G, u1, U1, u1Aux)); | |
Point Q = pubKey2Point(pubKey); | |
// U2 = u2 * Q | |
require(EC.isMul(Q, u2, U2, u2Aux)); | |
// P == U1 + U2 | |
require(EC.isSum(U1, U2, lambda, P)); | |
// cannot be identify | |
require(P != EC.ZERO); | |
require((P.x - r) % EC.n == 0); | |
} | |
// parse signature in DER format to get (r, s) pair | |
static function parseDERSig(Sig sig) : RSPair { | |
int rLen = unpack(sig[3 : 4]); | |
int r = fromBESigned(sig[4 : 4 + rLen]); | |
int sLen = unpack(sig[6 + rLen : 7 + rLen]); | |
int s = fromBESigned(sig[7 + rLen : 7 + rLen + sLen]); | |
return { r , s }; | |
} | |
// r & s are signed big endian | |
static function fromBESigned(bytes b) : int { | |
// convert big-endian to little-endian: either 32 or 33 bytes | |
bytes bLE = len(b) == 32 ? reverseBytes(b, 32) : reverseBytes(b, 33); | |
return unpack(bLE); | |
} | |
// convert public key to a point, assuming it's uncompressed | |
static function pubKey2Point(PubKey pubKey) : Point { | |
require(pubKey[: 1] == b'04'); | |
return { unpack(pubKey[1 : 33]), unpack(pubKey[33 : 65]) }; | |
} | |
} |
ECDSA Contract
***
NOTE:
[1] More precisely, it verifies signature against sighash.