Skip to content

Update onion tests to allow cross-testing of python and C implementations #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 7, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,16 @@ test-cli-tests: $(TEST_CLI_PROGRAMS)
set -e; cd test-cli; for args in "" --steal --unilateral --htlc-onchain; do scripts/setup.sh && scripts/test.sh $$args && scripts/shutdown.sh; done

test-onion: test/test_onion test/onion_key
set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(for k in `seq 20`; do test/onion_key $$k; done | cut -d: -f2) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key $$k | cut -d: -f1) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF
set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key --priv $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF

test-onion2: test/test_onion test/onion_key
set -e; TMPF=/tmp/onion.$$$$; python test/test_onion.py generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do test/test_onion --decode $$(test/onion_key --priv $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF

test-onion3: test/test_onion test/onion_key
set -e; TMPF=/tmp/onion.$$$$; test/test_onion --generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do python test/test_onion.py decode $$(test/onion_key --priv $$k) $$(test/onion_key --pub $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF

test-onion4: test/test_onion test/onion_key
set -e; TMPF=/tmp/onion.$$$$; python test/test_onion.py generate $$(test/onion_key --pub `seq 20`) > $$TMPF; for k in `seq 20`; do python test/test_onion.py decode $$(test/onion_key --priv $$k) $$(test/onion_key --pub $$k) < $$TMPF > $$TMPF.unwrap; mv $$TMPF.unwrap $$TMPF; done; rm -f $$TMPF

check: test-cli-tests test-onion

Expand Down
51 changes: 42 additions & 9 deletions test/onion_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ static void random_bytes(void *dst, size_t n)
d[i] = random() % 256;
}

#if 0
/* Compressed key would start with 0x3? Subtract from group. Thanks
* Greg Maxwell. */
static void flip_key(struct seckey *seckey)
Expand Down Expand Up @@ -47,6 +48,7 @@ static void flip_key(struct seckey *seckey)
seckey->u.be64[i] = cpu_to_be64(v);
}
}
#endif

#if 0
int main(int argc, char *argv[])
Expand Down Expand Up @@ -97,7 +99,7 @@ static void random_key(secp256k1_context *ctx,

/* We don't want to spend a byte encoding sign, so make sure it's 0x2 */
static void gen_keys(secp256k1_context *ctx,
struct seckey *seckey, struct onion_pubkey *pubkey)
struct seckey *seckey, struct compressed_pubkey *pubkey)
{
unsigned char tmp[33];
secp256k1_pubkey pkey;
Expand All @@ -108,29 +110,60 @@ static void gen_keys(secp256k1_context *ctx,
secp256k1_ec_pubkey_serialize(ctx, tmp, &len, &pkey,
SECP256K1_EC_COMPRESSED);
assert(len == sizeof(tmp));
#if 0
if (tmp[0] == 0x3)
flip_key(seckey);
memcpy(pubkey, tmp+1, sizeof(*pubkey));
#endif
memcpy(pubkey, tmp, sizeof(*pubkey));
}

int main(int argc, char *argv[])
void print_keypair(int pub, int priv)
{
secp256k1_context *ctx;
struct seckey seckey;
struct onion_pubkey pubkey;
struct compressed_pubkey pubkey;
char sechex[hex_str_size(sizeof(seckey))];
char pubhex[hex_str_size(sizeof(pubkey))];

if (argv[1])
srandom(atoi(argv[1]));
else
srandom(time(NULL) + getpid());
assert(pub || priv);

ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
gen_keys(ctx, &seckey, &pubkey);

hex_encode(&seckey, sizeof(seckey), sechex, sizeof(sechex));
hex_encode(&pubkey, sizeof(pubkey), pubhex, sizeof(pubhex));
printf("%s:%s\n", sechex, pubhex);

if (pub && priv) {
printf("%s:%s\n", sechex, pubhex);
} else {
printf("%s\n", (priv ? sechex : pubhex));
}
}

int main(int argc, char *argv[])
{
int pub = 1, priv = 1;

if (argc >= 1) {
if (strcmp(argv[1], "--pub") == 0) {
pub = 1; priv = 0;
argc--; argv++;
} else if (strcmp(argv[1], "--priv") == 0) {
pub = 0; priv = 1;
argc--; argv++;
}
}

if (argc <= 1) {
srandom(time(NULL) + getpid());
print_keypair(pub, priv);
} else {
int i;
for (i = 1; i < argc; i++) {
srandom(atoi(argv[i]));
print_keypair(pub, priv);
}
}

return 0;
}
5 changes: 5 additions & 0 deletions test/onion_key.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ struct seckey {
} u;
};

/* First byte is 0x02 or 0x03 indicating even or odd y */
struct compressed_pubkey {
unsigned char u8[33];
};

/* Prepend 0x02 to get pubkey for libsecp256k1 */
struct onion_pubkey {
unsigned char u8[32];
Expand Down
17 changes: 11 additions & 6 deletions test/test_onion.c
Original file line number Diff line number Diff line change
Expand Up @@ -588,20 +588,25 @@ bool peel_onion(struct onion *onion,
static bool parse_onion_pubkey(secp256k1_context *ctx,
const char *arg, secp256k1_pubkey *pubkey)
{
unsigned char tmp[33] = { 0x2 };
unsigned char tmp[33] = { };

if (!hex_decode(arg, strlen(arg), tmp + 1, sizeof(tmp) - 1))
if (!hex_decode(arg, strlen(arg), tmp, sizeof(tmp)))
return false;

return secp256k1_ec_pubkey_parse(ctx, pubkey, tmp, sizeof(tmp));
}

static char *make_message(const secp256k1_pubkey *pubkey)
static char *make_message(secp256k1_context *ctx,
const secp256k1_pubkey *pubkey)
{
char *m;
unsigned char tmp[33];
size_t len;
char hexstr[hex_str_size(20)];

hex_encode(pubkey, 20, hexstr, sizeof(hexstr));
secp256k1_ec_pubkey_serialize(ctx, tmp, &len, pubkey,
SECP256K1_EC_COMPRESSED);
hex_encode(tmp+1, 20, hexstr, sizeof(hexstr));
asprintf(&m, "Message for %s...", hexstr);
return m;
}
Expand Down Expand Up @@ -643,7 +648,7 @@ int main(int argc, char *argv[])
for (i = 1; i < argc; i++) {
if (!parse_onion_pubkey(ctx, argv[i], &pubkeys[i-1]))
errx(1, "Bad pubkey '%s'", argv[i]);
msgs[i-1] = make_message(&pubkeys[i-1]);
msgs[i-1] = make_message(ctx, &pubkeys[i-1]);
}

if (!create_onion(pubkeys, msgs, argc - 1, &onion))
Expand All @@ -670,7 +675,7 @@ int main(int argc, char *argv[])

if (!decrypt_onion(&seckey, &onion, &enckey, &pad_iv))
errx(1, "Failed decrypting onion for '%s'", argv[1]);
if (strncmp((char *)myhop(&onion)->msg, make_message(&pubkey),
if (strncmp((char *)myhop(&onion)->msg, make_message(ctx, &pubkey),
sizeof(myhop(&onion)->msg)))
errx(1, "Bad message '%s'", (char *)myhop(&onion)->msg);
if (!peel_onion(&onion, &enckey, &pad_iv))
Expand Down
102 changes: 59 additions & 43 deletions test/test_onion.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python

import argparse
import sys
import time

from hashlib import sha256
from binascii import hexlify, unhexlify
Expand Down Expand Up @@ -139,6 +141,16 @@ def get_pos_y_for_x(pubkey_x, yneg=0):
if pub_key_x is not None: OpenSSL.BN_free(pub_key_x)
if pub_key_y is not None: OpenSSL.BN_free(pub_key_y)

def ec_decompress(pubkey, curve='secp256k1'):
if pubkey[0] == '\x02' or pubkey[0] == '\x03':
yneg = ord(pubkey[0]) & 1
pubkey = "\x04" + pubkey[1:] + get_pos_y_for_x(pubkey[1:], yneg=yneg)
elif pubkey[0] == '\x04':
pass
else:
raise Exception("Unrecognised pubkey format: %s" % (pubkey,))
return pubkey

class Onion(object):
HMAC_LEN = 32
PKEY_LEN = 32
Expand Down Expand Up @@ -282,48 +294,52 @@ def __init__(self, msgs, pubkeys):
msgenc += hmac_sha256(hmacs[i], msgenc)
self.onion = msgenc

def decode_from_file(f):
keys = []
msg = ""
for ln in f.readlines():
if ln.startswith(" * Keypair "):
w = ln.strip().split()
idx = int(w[2].strip(":"))
priv = unhexlify(w[3])
pub = unhexlify(w[4])
assert idx == len(keys)
keys.append(ecc.ECC(privkey=priv, pubkey=pub, curve='secp256k1'))
elif ln.startswith(" * Message:"):
msg = unhexlify(ln[11:].strip())
elif ln.startswith("Decrypting"):
pass
else:
print ln
assert ln.strip() == ""

assert msg != ""
for k in keys:
o = OnionDecrypt(msg, k)
o.decrypt()
print o.msg
msg = o.fwd
print "done"
def generate(args):
server_keys = []
msgs = []
for k in args.pubkeys:
k = unhexlify(k)
msgs.append("Message for %s..." % (hexlify(k[1:21]),))
k = ec_decompress(k)
server_keys.append(k)
o = OnionEncrypt(msgs, server_keys)
sys.stdout.write(o.onion)
return

def decode(args):
msg = sys.stdin.read()
key = ecc.ECC(privkey=unhexlify(args.seckey),
pubkey=ec_decompress(unhexlify(args.pubkey)),
curve='secp256k1')
o = OnionDecrypt(msg, key)
o.decrypt()
#sys.stderr.write("Message: \"%s\"\n" % (o.msg,))
want_msg = "Message for %s..." % (args.pubkey[2:42])
if o.msg != want_msg + "\0"*(Onion.MSG_LEN - len(want_msg)):
raise Exception("Unexpected message: \"%s\" (wanted: %s)" % (o.msg, want_msg))

sys.stdout.write(o.fwd)

def main(argv):
parser = argparse.ArgumentParser(description="Process some integers.")
sp = parser.add_subparsers()
p = sp.add_parser("generate")
p.add_argument("pubkeys", nargs='+', help="public keys of recipients")
p.set_defaults(func=generate)

p = sp.add_parser("decode")
p.add_argument("seckey", help="secret key for router")
p.add_argument("pubkey", help="public key for router")
p.set_defaults(func=decode)

args = parser.parse_args(argv)

return args.func(args)




if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "generate":
if len(sys.argv) == 3:
n = int(sys.argv[2])
else:
n = 20
servers = [ecc.ECC(curve='secp256k1') for _ in range(n)]
server_pubs = [s.get_pubkey() for s in servers]
msgs = ["Howzit %d..." % (i,) for i in range(n)]

o = OnionEncrypt(msgs, server_pubs)

for i, s in enumerate(servers):
print " * Keypair %d: %s %s" % (
i, hexlify(s.privkey), hexlify(s.get_pubkey()))
print " * Message: %s" % (hexlify(o.onion))
else:
decode_from_file(sys.stdin)
main(sys.argv[1:])
sys.exit(0)