Freenode sucks

I logged into freenode, which I haven't logged into in a while, to help stop some trolling on #bittorrent. I couldn't identify as my nick any more, so I went to ask for help...

<mquin> when did you register it?
 <bramm> uh, circa 2000
 <mquin> the current registration is a little over 6 months old - if you did register it in 2000 it must have expired since
 <bramm> so someone just stole my nick?
 <bramm> since when does this thing expire registrations?
 <bramm> I'm pretty sure I've logged in within the last year
 <bramm> given that I've always had this nick, and that there are only two people named Bram which are recognizable names in open source, and that my name is one of the most well known in open source, I'd like my nick back :-P
 <mquin> nicks are considered expired after 60 days of inactivity, after which they can be dropped either on request or when we ocassionally clean up the services database
 <bramm> also, there's a problem that I'm an op on a channel, and need to give access in it to other people
 <bramm> that policy is completely retarded
 <bramm> the #bittorrent channel is having a problem with trolls, and we need to get rid of them, and thanks to that lamebrained policy there's currently noone with sufficient ops privileges in the channel to do anything about it
 <mquin> I'm sorry you feel that way, it's not really reasonable for us to keep nickname registrations perpetually when they are not being used
 <bramm> get real. I've logged in within the last year, getting rid of them after six months is nuts
 <bramm> if nobody does anything about this I'm going to go public about it, freenode does NOT want the publicity of me being pissed off
 <bramm> er, after 60 days I meant, I've never heard of nick expiration on such a short time scale, from any site
 <bramm> I can easily prove who I am. I'm the well-known author of an important project and need my nick back to stop trolling in the project channel, is there anything which can be done about this or do I have to make a stink?
 <mquin> handing the nick back to you, even if I were able to do that, would not restore any channel access you had when you held the registration
 <mquin> channel access flags are dropped along with the account
 <bramm> well how can we get someone to have ops on the channel?
 <mquin> if you are an offical representative of the bittorrent project you can assert that by filing a group registration, which would allow you to reclaim #bittorrent
 <bramm> and how can I do that?
 <mquin> you may also wish to talk to the current channel registrant - he can add additional users to the access list at this point
 <mquin> oh, my mistake, it's been held
 <bramm> what do you mean held?
 <bramm> maybe you missed that part about me being the channel registrant
 <bramm> and my nick being stolen
 <mquin> yes, I misread something I was looking at - my mistake
 <mquin> to avoid primary namespace (single-#) channels being lost in sitations such as this we transfer them to staff control in the event of the founder's nick being dropped
 <mquin> it makes it fairly straightforward to reassign them when there is a group registration rather than having them appear to be available for re-registration by anyone
 <bramm> I have never, in my entire life, heard of a registration expiration process which was this aggresive, or this cavalier about damaging existing relationships
 <mquin> the 60 days figure is just a minimum - we normally allow more grace (typically 1 week per year) for long standing registraions when processing drops by hand
 <bramm> you say that as if adding a few weeks to the end would make the time frame reasonable
 <mquin> we don't feel it is reasonable to hold nickname registrations perpetually if they are not being used
 <bramm> I'm not asking for perpetually
 <bramm> just something vaguely reasonable
 <bramm> and I hope you realize that you just completely pissed off one of the most well known and respected people in the whole open source community
 <mquin> I'm sorry you are upset
 <bramm> I'm just going to pretend you're a robot and not blow my stack at you
 <bramm> but it's requiring effort
 <mquin> What do you expect me to do? I can't very well return a nick to you that has been in use by someone else for well over 6 months.
 <bramm> well maybe the policies could have kept that person from taking over the nick, seeing as how I was using it for NINE YEARS prior to that
 <mquin> Had we known at the time that you were planning to be away from the network for an extended period of time we could have arranged for it to be held for you
 <mquin> I know it's unfortunate to lose a long-standing registration, but we do have to have some limit on what we consider a reasonable activity level
 <bramm> I was never informed of there being any such policy. I was never informed via email than my nick was about to expire. Any minimal checking of expirations being done by hand, which you say it is, would have indicated that my nick should absolutely not have been expired
 <mquin> unfortuantely it's difficult to verify which steps were or were not taken this long after the event

[Update] Well now that I've managed to get called an asshole (hi, HackerNews commenters who registered five minutes ago!) Here are my calmer thoughts

The reason I posted the log verbatim, me being pissed off and all, is that I wanted to make very clear that I was accurately representing official freenode policy, and that requesting help through support leads nowhere. My gripe is with freenode policy, which is asinine, not with the particular person I spoke to, who was merely being useless and patronizing.

The reason I got pissed wasn't because of the nick loss, which I find mildly annoying, but because channel ops got blown away, causing me to have to deal with this bullshit instead of just giving ops to someone else.

Yes I can be blunt. If you value the superficial affectation of politeness over the essential point of what someone is saying, you can shove it. I don't appreciate people saying that I'm this way because of asperger's, it just causes other people to whine that they're being oppressed because they can't criticize me. The whole line of argument is stupid. People are free to criticize me for not being polite, and I'm free to respond that they're being petty and superficial.

The whole 'it's free so you can't complain' argument is bullshit. There are plenty of free things which are of negative value to society because they suck up or distract resources which could be working on a much better alternative. I've provided lots of support for free stuff myself, both via employees and directly, and never have I claimed that a problem won't be fixed because the person airing a legitimate gripe hasn't gone through arbitrary bureaucratic processes, or that the person complaining should implement it themselves because they're a programmer, or refused to acknowledge that some pain a user experienced through no fault of their own really was unfortunate. And I always prioritize up users who matter and problems which need immediate fixing. That's the way you run things if you actually care about providing a valuable service.

As far as whether my ops problem might get resolved, whether I'd utterly cursed out the guy from support or had the humility of a saint, it probably wouldn't get handled regardless.

[Update 2] Some commenters don't seem to understand that Freenode policy, in fact Freenode's whole foundation for legitimacy, is that project leaders are entitled to control their channels. I am in fact a project leader with a long established channel, and in the time that site op spent pedantically repeating rules and procedures he could have verified who I was and fixed the situation, which, say what you will about lilo, is something he actually would do. I was not making any claim to importance which I don't unambiguously have, and my message to other programmers considering using public servers is that OFTC is down the hall and to the left.

Threading in web forums

Threading in web forums isn't hard to get right, but it continues to be done right approximately nowhere. In the interests of helping to improve the situation, I will now explain the right way to do things.

When returning to a web forum, what should be displayed are either all posts which are either new, or which are immediate ancestors to a new post. The non-new posts should be displayed grayed out, since they're there for context, to avoid the annoying practice of people having to manually edit quoting in improperly threaded systems.

Posts should be displayed properly threaded, with indenting used to indicate responses. But the standard simple way of indenting each response one level more isn't quite right, because it leads to way too much indenting. What should happen is that if there is more than one response to a post currently displayed then the responses get extra indentation, but if there's only one displayed then it gets the same level, with a graphic included to indicate that it's a response rather than a separate post. Note that this only takes into account posts which are currently displayed. Not currently displayed posts are irrelevant and shouldn't affect formatting.

There should of course be indicators and expanders for ancestors and undisplayed responses to displayed posts, and those should change the formatting of everything appropriately when hit.

If someone views a thread which has no new posts, they should simply be shown the entire thread.

There, that wasn't so complicated. Now please get it right!

Comments on Go

Here are my preliminary thoughts on the Go programming language.

The most interesting feature for me personally is the built-in threading. Aside from its superb support for multi-core, it's just plain a good set of ways of doing networking. The lack of a decent built-in networking library (and generally coordination library) in Python has been a thorn in my side just about forever. In particular the promotion of queues to being one of the few built-in primitives with their own special syntax encourages good threading practice and is clearly warranted. Even such a simple command as 'wait until either the UI thread or the networking thread comes up with something' is a source of ongoing pain in most languages, but is built into Go as a core concept using select.

Go seems to finally get the static typing problem solved. Its := operator is a reasonable middle point between C++'s ludicrous verbosity and ML's excessive magic. Types being structural is also a huge win. There's no end of stupid architectural haggling over what module a base type sits in and who owns it, and the answer 'nowhere' completely gets rid of that problem. It seems to me that there are deep subtle problems with such declarations - for example, how does it statically check that the parameters accepted by methods of a type you're receiving are compatible with what you want to pass them? But maybe I just haven't thought about it enough. It's too bad that Go doesn't currently have generics. I for one won't start any new project in it until it reaches that level of maturity.

Go's lack of exception handling is a real problem, and another thing I'm blocking on to do real development in it. My own preferred method for adding it would be that if you call a function which has multiple return values and you don't handle one of them, it defaults to shorting to the same return of the caller, although some people might complain about that being too much like Java's 'throws'. That said, I've gotten so used to debugging by stack trace that I'd be loathe to not have stack building built into the language in some form, and in fact I've gotten really attached to a tricked out logging tool I wrote which can decorate any object and automatically logs a full stack trace of every assignment which is made to the object and allows you to print them all out at once. But perhaps such trickery is really the domain of highly dynamic languages, and not appropriate for something as low level and performance oriented as Go.

The primitives in Go are quite good. All languages should have special maps and lists built in. I think it actually doesn't go far enough with giving them special status, and should have Python-style special syntax for maps. The curly brackets could be freed up by simply eliminating their current usage and making formatting have syntax. It's more than a little bit absurd that the language designers themselves have a setup where a utility standardizes the formatting of their own code every time they commit, but they still maintain the nominal free-form nature of the language. Really guys, I know you were traumatized by Fortran's original awful enforced formatting, but that was a long time ago and it's time to let go.

That said, the primitives are given too much special status in other ways - they're the only things which have type parameterization, making it impossible to even implement their interfaces yourself, and worse, they're the only things which are call by reference. The call by reference thing worries me a lot. I really, really don't want Go to become the reference/pointer mix hell which C++ has become, but it's already headed in that direction. It really shouldn't matter that much - things which are passed are either an address or a copy, and the reference/pointer distinction really just has to do with what's the default (okay, so typically references don't let you overwrite either, but that's not a fundamental property). I for one strongly prefer the default be an address, and clearly when push comes to shove Go's designers do too, but more important than which way it is is that it should be consistent. Already transitioning to something consistent might require rewriting huge amounts of code, and it's getting worse, so fixing this problem might have to happen soon or never, and I'm afraid that it might already be never.

Go's speed of compilation is very nice, although I'm afraid I view that not so much as a strength of Go but as an awfulness of C++. Why C++ continues to take forever to compile even on machines many orders of magnitude faster than the first ones I ever used it on has long been a mystery to me. I hope the answer is simply that it's a language which wasn't designed with ease of parsing in mind, and has a whole layer of preprocessing on top of it which is horribly abused.

It's interesting that Go is going the garbage-collected route. If such a low-level language as Go can get away with that (and, truth be known, their preferred garbage collector isn't really integrated yet, so it's a little early to call it) then we may never see another non-garbage-collected language ever again.

I despise the use of initial capital letters to specify that something is public. Maybe if I used it for a while I'd learn to not hate it, but for now I hate it. Does chinese even have uppercase?

It's entirely possible that after using Go for a while something else would really start to gnaw at me about it, but it generally has a good smell, so hopefully not.

If you've read this far, you should follow me on Twitter.

Dual Rings is on the market

This is my 'Dual Rings' puzzle, the result of a bizarre collaboration between myself and Oskar where I came up with a completely abstract puzzle and he came up with an unexpected mechanism for it. It's going on the market produced by Hanayama now, and there are a few copies you can buy immediately (see video info for details).

Google suggests life's most important questions

Thanks to google search suggestions, here's the list of life's most pressing existential questions:

How can...
How can you tell if a guy likes you?
How can you tell if a girl is a virgin?
How can I make my hair grow faster?
How can you tell if a girl likes you?
How can you tell if you're pregnant?
How can I get pregnant?
How can I keep from singing lyrics?
How can you tell if someone is lying?
How can I lose weight fast?
How can I keep from singing?

Am I...
Am I pregnant?
Am I fat?
Am I in love?
Am I overweight?
Am I depressed?
Am I having a boy or girl?
Am I an alcoholic?
Am I bipolar?

How long...
How long does implantation bleeding last?
How long does it take to get a passport?
How long does alcohol stay in your system?
How long does it take to get pregnant?
How long does sperm live?

Why do...
Why do men have nipples?
Why do dogs eat grass?
Why do cats purr?
Why do dogs eat poop?
Why do men cheat?
Why do we dream?
Why do we yawn?
Why do mosquito bites itch?
Why do cats knead?
Why do dogs lick people?

When can...
When can you get pregnant?
When can I take a pregnancy test?
When can you find out the gender of the baby?
When can you take a pregnancy test?
When can you feel the baby move?
When can I get pregnant?
When can you hear a baby's heartbeat?
When can you tell the gender of a baby?
When can babies eat eggs?
When can I take a home pregnancy test?

When will...
When will I die?
When will the world end?
When will I get married?
When will windows 7 be released?
When will xbox live be back up?
When will iphone 3.0 be released?
When will the recession end?

Can someone...
Can someone tell if you are looking at their facebook page?
Can someone get pregnant during their period?
Can someone spy on my computer?
Can someone tell if you look at their myspace page?
Can someone be allergic to water?
Can someone die from a broken heart?
Can someone find out my ip address?
Can someone see if I looked at their profile on facebook?
Can someone track my ip address?
Can someone tell if you searched them on facebook?

Can you...
Can you run it?
Can you get pregnant on your period?
Can you duet?
Can you feel the love tonight lyrics?
Can you be pregnant and still have a period?
Can you get pregnant right after your period?
Can you get pregnant right before your period?
Can you get pregnant from being fingered?
Can you have your period and still be pregnant?
Can you get pregnant if he pulls out?

The less existential prefixes have a lot of questions about Michael Jackson.

How come Michael Jackson is white?
Did Michael Jackson die?
Did Michael Jackson bleach his skin?
Did Michael Jackson write his own songs?
Did Michael Jackson molest children?
What are Michael Jackson's kids names?
What is up with Jermaine Jackson's hair?
Where will Michael Jackson be buried?
Where did Michael Jackson die?
Where did Michael Jackson live?

(no subject)

This post unintentionally demonstrates that functional-style Python is ugly and bad.

Let us start at the top. At the beginning, it has some sample code which defines a multiple() function, which could be trivially inlined, resulting in code which looks like this (I'm doing all examples in Python 3):
print(sum(x for x in range(1, 1000+1) if x % 3 == 0 or x % 5 == 0))

There's no reason whatsoever to expand that out. This should be an early indication that maybe the code samples here aren't the greatest.

Moving on, there's getting the sum of all fibonacci numbers less than 4 million. This is done in the example using itertools and yield, resulting in a fair amount of ugly code. Here's how a sane person does it:
def fibsum():
	a, b, c = 0, 1, 1
	total = 0
	while c < 4000000:
		total += c
		a, b, c = b, c, b + c
	return total

Now that's much more readable, flexible, and maintainable.

Finally, there's the problem of finding the largest palindrome which is the product of two three digit numbers. Here's my solution, which contains less code, is much more readable, and oh yeah, I threw in an optimization to make it return almost instantly instead of having to crunch for a second:
def bigpalindrome():
	best = 0
	for i in range(999, 0, -1):
		if i * 999 < best:
			return best
		for j in range(999, i-1, -1):
			x = list(str(i*j))
			if int(''.join(x)) == i*j:
				best = i*j

Much better. I think these examples do a good job of exploding the idea that the functional style of programming is clearly better and the appropriate first thing to teach people. Obviously some people are being driven to write horribly contorted and ugly code because they started in a functional language when they switch a more, ahem, mainstream one.

print isn't even vaguely thread safe

Thanks to everyone who provided suggestions for my problems with print yesterday. It turns out that print isn't even vaguely threadsafe, and that the rather surprising behavior when there's a collision is for there to be duplicate output bytes. So if you ever see duplicate print lines, you know what to guess it is now.

My quick hack for getting around the problem is to make a module called rprint which looks like this (I'm using python 3):
from threading import Lock

mylock = Lock()
p = print

def print(*a, **b):
	with mylock:
		p(*a, **b)
Then from every module where I have a print I say:
from rprint import print
I don't know if this is an elegant solution or an ugly hack, but hey it works.

print() experiencing deja vu

I have a very multithreaded Python3 app I'm running which I have a whole bunch of calls to print() in. I don't generally do multithreading, but this is spinning up a whole bunch of servers to check if they're working right. I have another test which is non-multithreaded and reproducible, and will of course have another test of actually running everything on multiple machines, but this is the intermediate step.

Anyhow, my problem is that print() appears to be sometimes causing the same line to be printed out repeatedly. I'm loathe to draw the conclusion that the underlying libraries are misbehaving, but I've taken the following precautions:

The amount of data it's spitting out is extensive
The code is asserting that the exact string hasn't been printed before
The thread and object id's are included in what's printed
There's a call to randrange() included at the end, just to make sure

And yet, I'm still getting identical lines of output.

My questions are:

What the fuck?

Is there some config setup to make this stop?

If not, is there a workaround?

My next step is to try making everything dump objects to be printed on a queue and have a single thread to all the printing, in the hopes that that will get the duplicates to go away, or at least be next to each other instead of obnoxiously interleaved. But first I'm taking a break for lunch, I've already wasted a day on this crap.

Sudoku and the way of the SAT solver

Writing a Sudoku solver is a slightly tricky but reasonably straightforward problem. Here I'll present a non-obvious way of implementing it which is short in terms of lines of code and also much easier to modify and extend than the more direct approaches. The general technique is applicable to a very wide range of problems, so hopefully someone will read this and then find it useful in the future.

My general approach is in multiple steps. First, express the Sudoku problem as a much more general type of problem called a SAT problem (short for 'satisfiability', traditionally capitalized for no particular reason). Then solve the SAT problem, then translate the solution back into the original format.

A SAT problem works on boolean variables, and the problem is to find a find an assignment which satisfies every one of a list of clauses. An individual clause states that at least one of a set of variables an their negations must be true, for example 'A or B or C' or 'not B on not D or E' are both typical clauses. To translate a Sudoku problem into a SAT problem, I make one boolean variable for each possible state of each cell of the original problem, so there's one variable for whether the upper-left cell is a one, another for whether it's a two, etc. Translating the constraints on the Sudoku problem is then straightforward - each digit must occur exactly once in each row, column, and subsquare, and each pair of cells in each row, column, or subsquare must not both be true (expressed as, either the first one is false or the second one is false).

To solve the SAT problem, the solver first scans for a clause containing no variables. If there is one, then the problem is insoluble and you return. Otherwise, it scans for clauses containing exactly one variable. If there is such a thing, it makes that assignment and recurses. If not, it finds the clause with the smallest number of variables in it (skipping the 'not A or not B' clauses, because everything contains about the same number of those) and recurses twice, setting one of the variables to true and false.

The advantage of this approach is that the heuristics for noticing when there's only one possible value left on a row, column, subsquare, or an individual cell don't have to be special cased - they're all subsumed by the single simple deduction that if a clause only contains a single variable, then that assignment has to be made. As a result, adding for example a constraint that the diagonals have to have each digit occur only once is trivial, and making irregular shapes and adding novel types of constraints is also fairly easy, while it would be quite difficult in the more obvious but less flexible approaches to writing Sudoku solvers.

It would be possible to make this code a lot faster, even without changing the architecture - for example, one could build an index of what variables occur in what clauses, to get rid of all the linear scans. But for solving a small number of Sudokus, or even a largeish number, taking a few seconds each is no big deal, and this approach is much more about maintainability, flexibility, and extensibility. Interestingly, for a large number of real world problems the fastest known way to solve them is to translate to SAT and run a general SAT solver with appropriately tuned parameters on the result. Obviously this is a hack, but applying custom heuristics to the unencoded problem is so messy and difficult, and so much work has been done on fine-tuning the general purpose solvers, that more often than not the theoretically better appoach can't compete in practice. Sudoku has some very special properties which tip the balance in favor of a very special custom bit of code, and in fact I made my SAT solver do a heuristic which is very Sudoku specific and skipped the common one that if a variable only appears in the positive or negative then you assign it because that doesn't apply, but such customization being warranted is highly unusual, and even in this case the general SAT solver has some clear advantages, starting with it containing fewer lines of code.

I used exception handling to indicate when a solution is found, and add assigments to it in handle and reraise clauses, mostly to make the point that this is a perfectly valid and highly pragmatic way of using exceptions.

A flat file of the code can be found here, but I've included it verbatim below.

By the way, the method of testing I used on this is to run it on a Sudoku problem with no initial constraints, which is a simple one-line test and does a good job of hitting all the edge cases regardless of the implementation details.

class SolutionFound(BaseException):
	def __init__(self):
		self.s = []

def assign(clauses, val):
	n = []
	for c in clauses:
		if val in c:
		elif -val in c:
			n.append([x for x in c if x != -val])
	return n

def try_sat(clauses, val):
		solve_sat(assign(clauses, val))
	except SolutionFound, s:
		raise s

def solve_sat(clauses):
	if [] in clauses:
	if len(clauses) == 0:
		raise SolutionFound()
	for x in clauses:
		if len(x) == 1:
			try_sat(clauses, x[0])
	smallest = clauses[0]
	for c in clauses:
		if c[0] > 0 and (smallest[0] < 0 or len(smallest) > len(c)):
			smallest = c
	try_sat(clauses, smallest[0])
	try_sat(clauses, -smallest[0])

svals = [list(range(x, x+9)) for x in range(0, 81, 9)] + \
		[list(range(x, x+81, 9)) for x in range(9)] + \
		[[x,x+1,x+2,x+9,x+10,x+11,x+18,x+19,x+20] for x in [0,3,6,27,30,33,54,57,60]]

def group(c, p):
	for i in range(len(c)):
		for j in range(i):
			p.append([-c[i], -c[j]])

def groups(c, p):
	for i in range(9):
		group([x + 100 * i + 1 for x in c], p)

def decode(r):
	r2 = [[0 for x in range(9)] for y in range(9)]
	for v in r:
		if v > 0:
			pos = (v-1) % 100
			r2[pos // 9][pos % 9] = (v // 100) + 1
	return r2

def solve_sudoku(problem):
	p = []
	for i in range(81):
		group(list(range(i+1, 901, 100)), p)
	for s in svals:
		groups(s, p)
	pre = []
	for x in range(9):
		for y in range(9):
			if problem[x][y]:
				m = x * 9 + y + 100 * (problem[x][y] - 1) + 1
				p = assign(p, m)
	except SolutionFound, s:
		return decode(s.s + pre)
	return None

import sys

if __name__ == '__main__':
	tt = [[0] * 9 for i in range(9)]
	print('\n'.join(' '.join([str(p) for p in q]) for q in solve_sudoku(tt)))
	print(' ')
	tt[0][0] = 6
	tt[3][4] = 1
	print('\n'.join(' '.join([str(p) for p in q]) for q in solve_sudoku(tt)))

Signatures don't do what you think they do

Security people tend to think that we live in a secure world, one in which everyone is constantly auditing the behavior of everyone else, and the end result is widespread mutually ensured honesty. We don't even vaguely live in that world. We live in a trusting world, where most people are mostly good, and the need for auditing is much lower than it would be if everyone were greedy sociopathic automatons. I would not want to live in a world which worked that way, and it would probably not only be an unpleasant place to live, but an extremely unproductive one as well, as every attempt by anyone to get anything done would be destroyed by theft and corruption.

I say this not to engage in broad philosophizing but because I have a very concrete point to make about a very specific thing: signatures. People  think of signatures as being a strong form of physical evidence, useful in court proceeding for proving that a particular person really did sign a particular thing. While this belief being widespread does a good job of denying that things they signed are actually their signature, which is a good thing, the claimed difficulty of forging signatures is simply not true. Anyone can practice forging a signature from a few samples for a few hours and be able to do a passable replica. Anyone with decent skill who practices a bit can get quite skilled. And people aren't very consistent about how they sign their own signatures, making even legitimate matches sometimes look fake. Thumbprints would be far better as a piece of evidence.

Despite that, signatures are still very important and good at what they're used for. What is that? It's to make it clear that someone knows when they're entering into a binding agreement. You can't be forced into an effective contract just because you said 'yeah, whatever' when asked if you want to participate, and you can't be forced into a contract by being tricked into signing a document which says something different than what you think it says. The theory of contracts is based on parties mutually agreeing to be contractually bound, and requires they all go through sufficient ceremony that it's clear when a contract has been entered into (sometimes merchants can get into binding contracts much more easily, but that's because they're expected to be more savvy, the law is big on protecting little old ladies from being suckered).

For example, take the use of signatures for receiving packages. There isn't even a contract entered into when a package is signed for, but the reasoning behind it is the same - it's to make clear that the person receiving the package knew they were receiving a package, and not claim later that there was a misunderstanding. To the extent that the signature has any evidenciary power in this case, it's mostly in that people generally by default put down their real name, and since the delivery person generally doesn't even know what the potential name of a recipient might be, it's hard for someone to lie later and claim that no package was delivered at all.

The hoopla around cryptographic signatures is largely misplaced. Having signatures which were on a web page which clearly stated what was being indicated and the signature was done by moving the mouse like a pen in a drawing area would do a much better job of indicating what signatures are supposed to indicate, and probably be much easier to back up in court later.

Now somebody please explain this to Bruce Schneier, because he doesn't get it.