Return-Path: <ZmnSCPxj@protonmail.com> Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133]) by lists.linuxfoundation.org (Postfix) with ESMTP id 3878EC000E for <bitcoin-dev@lists.linuxfoundation.org>; Fri, 2 Jul 2021 23:58:25 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 1ADD8400EF for <bitcoin-dev@lists.linuxfoundation.org>; Fri, 2 Jul 2021 23:58:25 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org X-Spam-Flag: NO X-Spam-Score: -1.599 X-Spam-Level: X-Spam-Status: No, score=-1.599 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FROM_LOCAL_NOVOWEL=0.5, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001] autolearn=ham autolearn_force=no Authentication-Results: smtp2.osuosl.org (amavisd-new); dkim=pass (1024-bit key) header.d=protonmail.com Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id cxKcX-Uv0pVK for <bitcoin-dev@lists.linuxfoundation.org>; Fri, 2 Jul 2021 23:58:22 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 Received: from mail-40130.protonmail.ch (mail-40130.protonmail.ch [185.70.40.130]) by smtp2.osuosl.org (Postfix) with ESMTPS id 9A75D40259 for <bitcoin-dev@lists.linuxfoundation.org>; Fri, 2 Jul 2021 23:58:22 +0000 (UTC) Date: Fri, 02 Jul 2021 23:58:14 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com; s=protonmail; t=1625270299; bh=BwmJK0ioikbQymotCRGL3brfF8yJ2QLMPQYm95+Elm8=; h=Date:To:From:Reply-To:Subject:In-Reply-To:References:From; b=FNzdIFNoCZvb+M/s1JC1hGY9a8clto8XQ/rBnJdl7swvsUjRJxA13CmvtSnvj3RBF W3dGuhm+srlYMaHE7exA2CIr6sH1JoyLYsJN9kNQDtKsv2+ECxmFnuGo0LCWiXWOqX fws3K0DLk1D+RsvAL8ybpGaqWsQWZDNk8vUysPy8= To: Jeremy <jlrubin@mit.edu>, Bitcoin Protocol Discussion <bitcoin-dev@lists.linuxfoundation.org> From: ZmnSCPxj <ZmnSCPxj@protonmail.com> Reply-To: ZmnSCPxj <ZmnSCPxj@protonmail.com> Message-ID: <YEsEkExygpn5zEqfCXSt8duo9C0tgyx9YBTRejVn8ccwX2SQCPQVP5r2Nav6isQIbK8ED2Z-fYNwcN0VhXpxAIhCd3TWeU1et85cZFIVWdA=@protonmail.com> In-Reply-To: <CAD5xwhiqwqRjMboX8z_xapBq5=KOfP3eOSQzRcY-Cc7wq1gXUQ@mail.gmail.com> References: <CAD5xwhiqwqRjMboX8z_xapBq5=KOfP3eOSQzRcY-Cc7wq1gXUQ@mail.gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Subject: Re: [bitcoin-dev] CheckSigFromStack for Arithmetic Values X-BeenThere: bitcoin-dev@lists.linuxfoundation.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: Bitcoin Protocol Discussion <bitcoin-dev.lists.linuxfoundation.org> List-Unsubscribe: <https://lists.linuxfoundation.org/mailman/options/bitcoin-dev>, <mailto:bitcoin-dev-request@lists.linuxfoundation.org?subject=unsubscribe> List-Archive: <http://lists.linuxfoundation.org/pipermail/bitcoin-dev/> List-Post: <mailto:bitcoin-dev@lists.linuxfoundation.org> List-Help: <mailto:bitcoin-dev-request@lists.linuxfoundation.org?subject=help> List-Subscribe: <https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev>, <mailto:bitcoin-dev-request@lists.linuxfoundation.org?subject=subscribe> X-List-Received-Date: Fri, 02 Jul 2021 23:58:25 -0000 Good morning Jeremy, > Dear Bitcoin Devs, > > It recently occurred to me that it's possible to do a lamport signature i= n script for arithmetic values by using a binary expanded representation. T= here are some applications that might benefit from this and I don't recall = seeing it discussed elsewhere, but would be happy for a citation/reference = to the technique. > > blog post here, https://rubin.io/blog/2021/07/02/signing-5-bytes/, text r= eproduced below > > There are two insights in this post: > 1. to use a bitwise expansion of the number > 2. to use a lamport signature > Let's look at the code in python and then translate to bitcoin script: > ```python > def add_bit(idx, preimage, image_0, image_1): > =C2=A0 =C2=A0 s =3D sha256(preimage) > =C2=A0 =C2=A0 if s =3D=3D image_1: > =C2=A0 =C2=A0 =C2=A0 =C2=A0 return (1 << idx) > =C2=A0 =C2=A0 if s =3D=3D image_0: > =C2=A0 =C2=A0 =C2=A0 =C2=A0 return 0 > =C2=A0 =C2=A0 else: > =C2=A0 =C2=A0 =C2=A0 =C2=A0 assert False > def get_signed_number(witnesses : List[Hash], keys : List[Tuple[Hash, Has= h]]): > =C2=A0 =C2=A0 acc =3D 0 > =C2=A0 =C2=A0 for (idx, preimage) in enumerate(witnesses): > =C2=A0 =C2=A0 =C2=A0 =C2=A0 acc +=3D add_bit(idx, preimage, keys[idx][0],= keys[idx][1]) > =C2=A0 =C2=A0 return x > ``` > So what's going on here? The signer generates a key which is a list of pa= irs of > hash images to create the script. > To sign, the signer provides a witness of a list of preimages that match = one or the other. > During validation, the network adds up a weighted value per preimage and = checks > that there are no left out values. > Let's imagine a concrete use case: I want a third party to post-hoc sign = a sequence lock. This is 16 bits. > I can form the following script: > ``` > <pk> checksigverify > 0 > SWAP sha256 DUP <H(K_0_1)> EQUAL IF DROP <1> ADD ELSE <H(K_0_0)> EQUALVER= IFY ENDIF > SWAP sha256 DUP <H(K_1_1)> EQUAL IF DROP <1<<1> ADD ELSE <H(K_1_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_2_1)> EQUAL IF DROP <1<<2> ADD ELSE <H(K_2_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_3_1)> EQUAL IF DROP <1<<3> ADD ELSE <H(K_3_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_4_1)> EQUAL IF DROP <1<<4> ADD ELSE <H(K_4_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_5_1)> EQUAL IF DROP <1<<5> ADD ELSE <H(K_5_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_6_1)> EQUAL IF DROP <1<<6> ADD ELSE <H(K_6_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_7_1)> EQUAL IF DROP <1<<7> ADD ELSE <H(K_7_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_8_1)> EQUAL IF DROP <1<<8> ADD ELSE <H(K_8_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_9_1)> EQUAL IF DROP <1<<9> ADD ELSE <H(K_9_0)> EQUAL= VERIFY ENDIF > SWAP sha256 DUP <H(K_10_1)> EQUAL IF DROP <1<<10> ADD ELSE <H(K_10_0)> EQ= UALVERIFY ENDIF > SWAP sha256 DUP <H(K_11_1)> EQUAL IF DROP <1<<11> ADD ELSE <H(K_11_0)> EQ= UALVERIFY ENDIF > SWAP sha256 DUP <H(K_12_1)> EQUAL IF DROP <1<<12> ADD ELSE <H(K_12_0)> EQ= UALVERIFY ENDIF > SWAP sha256 DUP <H(K_13_1)> EQUAL IF DROP <1<<13> ADD ELSE <H(K_13_0)> EQ= UALVERIFY ENDIF > SWAP sha256 DUP <H(K_14_1)> EQUAL IF DROP <1<<14> ADD ELSE <H(K_14_0)> EQ= UALVERIFY ENDIF > SWAP sha256 DUP <H(K_15_1)> EQUAL IF DROP <1<<15> ADD ELSE <H(K_15_0)> EQ= UALVERIFY ENDIF > CHECKSEQUENCEVERIFY > ``` This took a bit of thinking to understand, mostly because you use the `<<` = operator in a syntax that uses `< >` as delimiters, which was mildly confus= ing --- at first I thought you were pushing some kind of nested SCRIPT repr= esentation, but in any case, replacing it with the actual numbers is a litt= le less confusing on the syntax front, and I think (hope?) most people who = can understand `1<<1` have also memorized the first few powers of 2.... > ``` > <pk> checksigverify > 0 > SWAP sha256 DUP <H(K_0_1)> EQUAL IF DROP <1> ADD ELSE <H(K_0_0)> EQUALVER= IFY ENDIF > SWAP sha256 DUP <H(K_1_1)> EQUAL IF DROP <2> ADD ELSE <H(K_1_0)> EQUALVER= IFY ENDIF > SWAP sha256 DUP <H(K_2_1)> EQUAL IF DROP <4> ADD ELSE <H(K_2_0)> EQUALVER= IFY ENDIF > SWAP sha256 DUP <H(K_3_1)> EQUAL IF DROP <8> ADD ELSE <H(K_3_0)> EQUALVER= IFY ENDIF > SWAP sha256 DUP <H(K_4_1)> EQUAL IF DROP <16> ADD ELSE <H(K_4_0)> EQUALVE= RIFY ENDIF > SWAP sha256 DUP <H(K_5_1)> EQUAL IF DROP <32> ADD ELSE <H(K_5_0)> EQUALVE= RIFY ENDIF > SWAP sha256 DUP <H(K_6_1)> EQUAL IF DROP <64> ADD ELSE <H(K_6_0)> EQUALVE= RIFY ENDIF > SWAP sha256 DUP <H(K_7_1)> EQUAL IF DROP <128> ADD ELSE <H(K_7_0)> EQUALV= ERIFY ENDIF > SWAP sha256 DUP <H(K_8_1)> EQUAL IF DROP <256> ADD ELSE <H(K_8_0)> EQUALV= ERIFY ENDIF > SWAP sha256 DUP <H(K_9_1)> EQUAL IF DROP <512> ADD ELSE <H(K_9_0)> EQUALV= ERIFY ENDIF > SWAP sha256 DUP <H(K_10_1)> EQUAL IF DROP <1024> ADD ELSE <H(K_10_0)> EQU= ALVERIFY ENDIF > SWAP sha256 DUP <H(K_11_1)> EQUAL IF DROP <2048> ADD ELSE <H(K_11_0)> EQU= ALVERIFY ENDIF > SWAP sha256 DUP <H(K_12_1)> EQUAL IF DROP <4096> ADD ELSE <H(K_12_0)> EQU= ALVERIFY ENDIF > SWAP sha256 DUP <H(K_13_1)> EQUAL IF DROP <8192> ADD ELSE <H(K_13_0)> EQU= ALVERIFY ENDIF > SWAP sha256 DUP <H(K_14_1)> EQUAL IF DROP <16384> ADD ELSE <H(K_14_0)> EQ= UALVERIFY ENDIF > SWAP sha256 DUP <H(K_15_1)> EQUAL IF DROP <32768> ADD ELSE <H(K_15_0)> EQ= UALVERIFY ENDIF > CHECKSEQUENCEVERIFY > ``` On the other hand LOL WTF, this is cool. Basically you are showing that if we enable something as innocuous as `OP_A= DD`, we can implement Lamport signatures for **arbitrary** values represent= able in small binary numbers (16 bits in the above example). I was thinking "why not Merkle signatures" since the pubkey would be much s= maller but the signature would be much larger, but (a) the SCRIPT would be = much more complicated and (b) in modern Bitcoin, the above SCRIPT would be = in the witness stack anyway so there is no advantage to pushing the size to= wards the signature rather than the pubkey, they all have the same weight, = and since both Lamport and Merkle are single-use-only and we do not want to= encourage pubkey reuse even if they were not, the Merkle has much larger s= ignature size, so Merkle sigs end up more expensive. Regards, ZmnSCPxj