From a0d8e40379bc968fb1788c4c83fa7625c0739fe8 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 20 Sep 2023 10:13:13 -0300 Subject: [PATCH] delete things to start fresh. --- LICENSE | 202 --------------------------------------- README.md | 20 +--- attestations.go | 180 ----------------------------------- operations.go | 158 ------------------------------ remote_calendar.go | 146 ---------------------------- remote_calendar_test.go | 70 -------------- serialize.go | 206 ---------------------------------------- serialize_test.go | 153 ----------------------------- timestamp.go | 183 ----------------------------------- util.go | 11 --- verifier.go | 86 ----------------- 11 files changed, 2 insertions(+), 1413 deletions(-) delete mode 100644 LICENSE delete mode 100644 attestations.go delete mode 100644 operations.go delete mode 100644 remote_calendar.go delete mode 100644 remote_calendar_test.go delete mode 100644 serialize.go delete mode 100644 serialize_test.go delete mode 100644 timestamp.go delete mode 100644 util.go delete mode 100644 verifier.go diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d645695..0000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md index a5bd49b..2eed4ce 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,7 @@ # opentimestamps -Go port of https://github.com/opentimestamps/python-opentimestamps. - -Copied from https://github.com/BlockchainSource/go-opentimestamps. - -# Done - -* Byte-level serialization format -* Timestamp parsing -* Creating pending timestamps -* Upgrading pending timestamps -* Bitcoin Timestamp verification - -# To do - -* Support for multiple timestamp servers -* Proper timestamp merging (on upgrade) -* More conformant serialization (sorting) +Interact with calendar servers, create and verify OTS attestations. # License -Apache 2.0 +Public Domain diff --git a/attestations.go b/attestations.go deleted file mode 100644 index 12f0858..0000000 --- a/attestations.go +++ /dev/null @@ -1,180 +0,0 @@ -package opentimestamps - -import ( - "bytes" - "fmt" -) - -const ( - attestationTagSize = 8 - attestationMaxPayloadSize = 8192 - pendingAttestationMaxUriLength = 1000 -) - -var ( - bitcoinAttestationTag = mustDecodeHex("0588960d73d71901") - pendingAttestationTag = mustDecodeHex("83dfe30d2ef90c8e") -) - -type Attestation interface { - tag() []byte - decode(*deserializationContext) (Attestation, error) - encode(*serializationContext) error -} - -type baseAttestation struct { - fixedTag []byte -} - -func (b *baseAttestation) tag() []byte { - return b.fixedTag -} - -type pendingAttestation struct { - baseAttestation - uri string -} - -func newPendingAttestation() *pendingAttestation { - return &pendingAttestation{ - baseAttestation: baseAttestation{ - fixedTag: pendingAttestationTag, - }, - } -} - -func (p *pendingAttestation) decode( - ctx *deserializationContext, -) (Attestation, error) { - uri, err := ctx.readVarBytes(0, pendingAttestationMaxUriLength) - if err != nil { - return nil, err - } - // TODO utf8 checks - ret := *p - ret.uri = string(uri) - return &ret, nil -} - -func (p *pendingAttestation) encode(ctx *serializationContext) error { - return ctx.writeVarBytes([]byte(p.uri)) -} - -func (p *pendingAttestation) String() string { - return fmt.Sprintf("VERIFY PendingAttestation(url=%s)", p.uri) -} - -type BitcoinAttestation struct { - baseAttestation - Height uint64 -} - -func newBitcoinAttestation() *BitcoinAttestation { - return &BitcoinAttestation{ - baseAttestation: baseAttestation{bitcoinAttestationTag}, - } -} - -func (b *BitcoinAttestation) String() string { - return fmt.Sprintf("VERIFY BitcoinAttestation(height=%d)", b.Height) -} - -func (b *BitcoinAttestation) decode( - ctx *deserializationContext, -) (Attestation, error) { - height, err := ctx.readVarUint() - if err != nil { - return nil, err - } - ret := *b - ret.Height = height - return &ret, nil -} - -func (b *BitcoinAttestation) encode(ctx *serializationContext) error { - return ctx.writeVarUint(uint64(b.Height)) -} - -const hashMerkleRootSize = 32 - -func (b *BitcoinAttestation) VerifyAgainstBlockHash(digest, blockHash []byte) error { - if len(digest) != hashMerkleRootSize { - return fmt.Errorf("invalid digest size %d", len(digest)) - } - if !bytes.Equal(digest, blockHash) { - return fmt.Errorf( - "hash mismatch digest=%x blockHash=%x", - digest, blockHash, - ) - } - return nil -} - -// This is a catch-all for when we don't know how to parse it -type unknownAttestation struct { - tagBytes []byte - bytes []byte -} - -func (u unknownAttestation) tag() []byte { - return u.tagBytes -} - -func (unknownAttestation) decode(*deserializationContext) (Attestation, error) { - panic("not implemented") -} - -func (unknownAttestation) encode(*serializationContext) error { - panic("not implemented") -} - -func (u unknownAttestation) String() string { - return fmt.Sprintf("UnknownAttestation(bytes=%q)", u.bytes) -} - -var attestations []Attestation = []Attestation{ - newPendingAttestation(), - newBitcoinAttestation(), -} - -func encodeAttestation(ctx *serializationContext, att Attestation) error { - if err := ctx.writeBytes(att.tag()); err != nil { - return err - } - buf := &bytes.Buffer{} - if err := att.encode(&serializationContext{buf}); err != nil { - return err - } - return ctx.writeVarBytes(buf.Bytes()) -} - -func ParseAttestation(ctx *deserializationContext) (Attestation, error) { - tag, err := ctx.readBytes(attestationTagSize) - if err != nil { - return nil, err - } - - attBytes, err := ctx.readVarBytes( - 0, attestationMaxPayloadSize, - ) - if err != nil { - return nil, err - } - attCtx := newDeserializationContext( - bytes.NewBuffer(attBytes), - ) - - for _, a := range attestations { - if bytes.Equal(tag, a.tag()) { - att, err := a.decode(attCtx) - if err != nil { - return nil, err - } - if !attCtx.assertEOF() { - return nil, fmt.Errorf("expected EOF in attCtx") - } - return att, nil - } - } - return unknownAttestation{tag, attBytes}, nil -} diff --git a/operations.go b/operations.go deleted file mode 100644 index 4aa9cd7..0000000 --- a/operations.go +++ /dev/null @@ -1,158 +0,0 @@ -package opentimestamps - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" -) - -const maxResultLength = 4096 - -type ( - unaryMsgOp func(message []byte) ([]byte, error) - binaryMsgOp func(message, argument []byte) ([]byte, error) -) - -// msgAppend returns the concatenation of msg and arg -func msgAppend(msg, arg []byte) (res []byte, err error) { - res = append(res, msg...) - res = append(res, arg...) - return -} - -// msgPrepend returns the concatenation of arg and msg -func msgPrepend(msg, arg []byte) (res []byte, err error) { - res = append(res, arg...) - res = append(res, msg...) - return -} - -// msgReverse returns the reversed msg. Deprecated. -func msgReverse(msg []byte) ([]byte, error) { - if len(msg) == 0 { - return nil, fmt.Errorf("empty input invalid for msgReverse") - } - res := make([]byte, len(msg)) - for i, b := range msg { - res[len(res)-i-1] = b - } - return res, nil -} - -func msgHexlify(msg []byte) ([]byte, error) { - if len(msg) == 0 { - return nil, fmt.Errorf("empty input invalid for msgHexlify") - } - return []byte(hex.EncodeToString(msg)), nil -} - -type opCode interface { - match(byte) bool - decode(*deserializationContext) (opCode, error) - encode(*serializationContext) error - apply(message []byte) ([]byte, error) -} - -type op struct { - tag byte - name string -} - -func (o op) match(tag byte) bool { - return o.tag == tag -} - -type unaryOp struct { - op - msgOp unaryMsgOp -} - -func newUnaryOp(tag byte, name string, msgOp unaryMsgOp) *unaryOp { - return &unaryOp{op{tag: tag, name: name}, msgOp} -} - -func (u *unaryOp) String() string { - return u.name -} - -func (u *unaryOp) decode(ctx *deserializationContext) (opCode, error) { - ret := *u - return &ret, nil -} - -func (u *unaryOp) encode(ctx *serializationContext) error { - return ctx.writeByte(u.tag) -} - -func (u *unaryOp) apply(message []byte) ([]byte, error) { - return u.msgOp(message) -} - -// Binary operations -// We decode an extra varbyte argument and use it in apply() - -type binaryOp struct { - op - msgOp binaryMsgOp - argument []byte -} - -func newBinaryOp(tag byte, name string, msgOp binaryMsgOp) *binaryOp { - return &binaryOp{ - op: op{tag: tag, name: name}, - msgOp: msgOp, - argument: nil, - } -} - -func (b *binaryOp) decode(ctx *deserializationContext) (opCode, error) { - arg, err := ctx.readVarBytes(0, maxResultLength) - if err != nil { - return nil, err - } - if len(arg) == 0 { - return nil, fmt.Errorf("empty argument invalid for binaryOp") - } - ret := *b - ret.argument = arg - return &ret, nil -} - -func (b *binaryOp) encode(ctx *serializationContext) error { - if err := ctx.writeByte(b.tag); err != nil { - return err - } - return ctx.writeVarBytes(b.argument) -} - -func (b *binaryOp) apply(message []byte) ([]byte, error) { - return b.msgOp(message, b.argument) -} - -func (b *binaryOp) String() string { - return fmt.Sprintf("%s %x", b.name, b.argument) -} - -func msgSHA256(msg []byte) ([]byte, error) { - res := sha256.Sum256(msg) - return res[:], nil -} - -var ( - opAppend = newBinaryOp(0xf0, "APPEND", msgAppend) - opPrepend = newBinaryOp(0xf1, "PREPEND", msgPrepend) - opReverse = newUnaryOp(0xf2, "REVERSE", msgReverse) - opHexlify = newUnaryOp(0xf3, "HEXLIFY", msgHexlify) - opSHA256 = newUnaryOp(0x08, "SHA256", msgSHA256) -) - -var opCodes []opCode = []opCode{opAppend, opPrepend, opReverse, opHexlify, opSHA256} - -func parseOp(ctx *deserializationContext, tag byte) (opCode, error) { - for _, op := range opCodes { - if op.match(tag) { - return op.decode(ctx) - } - } - return nil, fmt.Errorf("could not decode tag %02x", tag) -} diff --git a/remote_calendar.go b/remote_calendar.go deleted file mode 100644 index b2f0edb..0000000 --- a/remote_calendar.go +++ /dev/null @@ -1,146 +0,0 @@ -package opentimestamps - -import ( - "bytes" - "encoding/hex" - "fmt" - "io" - "net/http" - "net/http/httputil" - "strings" - - "github.com/sirupsen/logrus" -) - -const userAgent = "go-opentimestamps" - -const dumpResponse = false - -type RemoteCalendar struct { - baseURL string - client *http.Client - log *logrus.Logger -} - -func NewRemoteCalendar(baseURL string) (*RemoteCalendar, error) { - // FIXME remove this - if baseURL == "localhost" { - baseURL = "http://localhost:14788" - } - // TODO validate url - if !strings.HasSuffix(baseURL, "/") { - baseURL += "/" - } - return &RemoteCalendar{ - baseURL, - http.DefaultClient, - logrus.New(), - }, nil -} - -// Check response status, return informational error message if -// status is not `200 OK`. -func checkStatusOK(resp *http.Response) error { - if resp.StatusCode == http.StatusOK { - return nil - } - errMsg := fmt.Sprintf("unexpected response: %q", resp.Status) - if resp.Body == nil { - return fmt.Errorf("%s (body=nil)", errMsg) - } - defer resp.Body.Close() - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("%s (bodyErr=%v)", errMsg, err) - } else { - return fmt.Errorf("%s (body=%q)", errMsg, bodyBytes) - } -} - -func (c *RemoteCalendar) do(r *http.Request) (*http.Response, error) { - r.Header.Add("Accept", "application/vnd.opentimestamps.v1") - r.Header.Add("User-Agent", userAgent) - c.log.Debugf("> %s %s", r.Method, r.URL) - resp, err := c.client.Do(r) - if err != nil { - c.log.Errorf("> %s %s error: %v", r.Method, r.URL, err) - return resp, err - } - c.log.Debugf("< %s %s - %v", r.Method, r.URL, resp.Status) - if dumpResponse { - bytes, err := httputil.DumpResponse(resp, true) - if err == nil { - c.log.Debugf("response dump:%s ", bytes) - } - } - return resp, err -} - -func (c *RemoteCalendar) url(path string) string { - return c.baseURL + path -} - -func (c *RemoteCalendar) Submit(digest [32]byte) (*Timestamp, error) { - body := bytes.NewBuffer(digest[:]) - req, err := http.NewRequest("POST", c.url("digest"), body) - if err != nil { - return nil, err - } - resp, err := c.do(req) - if err != nil { - return nil, err - } - if resp.Body != nil { - defer resp.Body.Close() - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("expected 200, got %v", resp.Status) - } - return NewTimestampFromReader(resp.Body, digest[:]) -} - -func (c *RemoteCalendar) GetTimestamp(commitment []byte) (*Timestamp, error) { - url := c.url("timestamp/" + hex.EncodeToString(commitment)) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - resp, err := c.do(req) - if err != nil { - return nil, err - } - if err := checkStatusOK(resp); err != nil { - return nil, err - } - if resp.Body != nil { - defer resp.Body.Close() - } - return NewTimestampFromReader(resp.Body, commitment) -} - -type PendingTimestamp struct { - Timestamp *Timestamp - PendingAttestation *pendingAttestation -} - -func (p PendingTimestamp) Upgrade() (*Timestamp, error) { - cal, err := NewRemoteCalendar(p.PendingAttestation.uri) - if err != nil { - return nil, err - } - return cal.GetTimestamp(p.Timestamp.Message) -} - -func PendingTimestamps(ts *Timestamp) (res []PendingTimestamp) { - ts.Walk(func(ts *Timestamp) { - for _, att := range ts.Attestations { - p, ok := att.(*pendingAttestation) - if !ok { - continue - } - attCopy := *p - res = append(res, PendingTimestamp{ts, &attCopy}) - } - }) - return -} diff --git a/remote_calendar_test.go b/remote_calendar_test.go deleted file mode 100644 index 306980a..0000000 --- a/remote_calendar_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package opentimestamps - -import ( - "crypto/sha256" - "fmt" - "os" - "testing" - "time" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - calendarServerEnvvar = "GOTS_TEST_CALENDAR_SERVER" - bitcoinRegtestEnvvar = "GOTS_TEST_BITCOIN_REGTEST_SERVER" -) - -func newTestCalendar(url string) *RemoteCalendar { - logrus.SetLevel(logrus.DebugLevel) - cal, err := NewRemoteCalendar(url) - if err != nil { - panic("could not create test calendar") - } - cal.log.Level = logrus.DebugLevel - return cal -} - -func newTestDigest(in string) [32]byte { - return sha256.Sum256([]byte(in)) -} - -func TestRemoteCalendarExample(t *testing.T) { - dts, err := NewDetachedTimestampFromPath( - "./examples/two-calendars.txt.ots", - ) - require.NoError(t, err) - - pts := PendingTimestamps(dts.Timestamp) - assert.Equal(t, 2, len(pts)) - for _, pt := range pts { - ts, err := pt.Upgrade() - assert.NoError(t, err) - fmt.Print(ts.Dump()) - } -} - -func TestRemoteCalendarRoundTrip(t *testing.T) { - calendarServer := os.Getenv(calendarServerEnvvar) - if calendarServer == "" { - t.Skipf("%q not set, skipping test", calendarServerEnvvar) - } - cal := newTestCalendar(calendarServer) - ts, err := cal.Submit(newTestDigest("Hello, World!")) - require.NoError(t, err) - require.NotNil(t, ts) - - // TODO call rpcclient generateblock 100 - - // FIXME possible opentimestamps-server bug? - // wait until attestation has been aggregated - time.Sleep(2 * time.Second) - - for _, pts := range PendingTimestamps(ts) { - ts, err := pts.Upgrade() - assert.NoError(t, err) - _ = ts - } -} diff --git a/serialize.go b/serialize.go deleted file mode 100644 index 85e677f..0000000 --- a/serialize.go +++ /dev/null @@ -1,206 +0,0 @@ -package opentimestamps - -import ( - "bufio" - "bytes" - "fmt" - "io" - "math" -) - -// serializationContext helps encoding values in the ots format -type serializationContext struct { - w io.Writer -} - -// newSerializationContext returns a serializationContext for a writer -func newSerializationContext(w io.Writer) *serializationContext { - return &serializationContext{w} -} - -// writeBytes writes the raw bytes to the underlying writer -func (s serializationContext) writeBytes(b []byte) error { - // number of bytes can be ignored - // if it is equal len(b) then err is nil - _, err := s.w.Write(b) - if err != nil { - return err - } - return nil -} - -// writeByte writes a single byte -func (s serializationContext) writeByte(b byte) error { - return s.writeBytes([]byte{b}) -} - -// writeBool encodes and writes a boolean value -func (s serializationContext) writeBool(b bool) error { - if b { - return s.writeByte(0xff) - } else { - return s.writeByte(0x00) - } -} - -// writeVarUint encodes and writes writes a variable-length integer -func (s serializationContext) writeVarUint(v uint64) error { - if v == 0 { - s.writeByte(0x00) - } - for v > 0 { - b := byte(v & 0x7f) - if v > uint64(0x7f) { - b |= 0x80 - } - if err := s.writeByte(b); err != nil { - return err - } - if v <= 0x7f { - break - } - v >>= 7 - } - return nil -} - -// writeVarBytes encodes and writes a variable-length array -func (s serializationContext) writeVarBytes(arr []byte) error { - if err := s.writeVarUint(uint64(len(arr))); err != nil { - return err - } - return s.writeBytes(arr) -} - -// deserializationContext helps decoding values from the ots format -type deserializationContext struct { - r io.Reader -} - -// safety boundary for readBytes -// allocation limit for arrays -const maxReadSize = (1 << 12) - -func (d deserializationContext) dump() string { - arr, _ := d.r.(*bufio.Reader).Peek(512) - return fmt.Sprintf("% x", arr) -} - -// readBytes reads n bytes. -func (d deserializationContext) readBytes(n int) ([]byte, error) { - if n > maxReadSize { - return nil, fmt.Errorf("over maxReadSize: %d", maxReadSize) - } - b := make([]byte, n) - m, err := d.r.Read(b) - if err != nil { - return b, err - } - if n != m { - return b, fmt.Errorf("expected %d bytes, got %d", n, m) - } - return b[:], nil -} - -// readByte reads a single byte. -func (d deserializationContext) readByte() (byte, error) { - arr, err := d.readBytes(1) - if err != nil { - return 0, err - } - return arr[0], nil -} - -// readBool reads a boolean. -func (d deserializationContext) readBool() (bool, error) { - arr, err := d.readBytes(1) - if err != nil { - return false, err - } - switch v := arr[0]; v { - case 0x00: - return false, nil - case 0xff: - return true, nil - default: - return false, fmt.Errorf("unexpected value %x", v) - } -} - -// readVarUint reads a variable-length uint64. -func (d deserializationContext) readVarUint() (uint64, error) { - // NOTE - // the original python implementation has no uint64 limit, but I - // don't think we'll ever need more that that. - val := uint64(0) - shift := uint(0) - for { - b, err := d.readByte() - if err != nil { - return 0, err - } - shifted := uint64(b&0x7f) << shift - // ghetto overflow check - if (shifted >> shift) != uint64(b&0x7f) { - return 0, fmt.Errorf("uint64 overflow") - } - val |= shifted - if b&0x80 == 0 { - return val, nil - } - shift += 7 - } -} - -// readVarBytes reads variable-length number of bytes. -func (d deserializationContext) readVarBytes(minLen, maxLen int) ([]byte, error) { - v, err := d.readVarUint() - if err != nil { - return nil, err - } - if v > math.MaxInt32 { - return nil, fmt.Errorf("int overflow") - } - vint := int(v) - if maxLen < vint || vint < minLen { - return nil, fmt.Errorf( - "varbytes length %d outside range (%d, %d)", - vint, minLen, maxLen, - ) - } - - return d.readBytes(vint) -} - -// assertMagic removes reads the expected bytes from the stream. Returns an -// error if the bytes are unexpected. -func (d deserializationContext) assertMagic(expected []byte) error { - arr, err := d.readBytes(len(expected)) - if err != nil { - return err - } - if !bytes.Equal(expected, arr) { - return fmt.Errorf( - "magic bytes mismatch, expected % x got % x", - expected, arr, - ) - } - return nil -} - -// assertEOF reads a byte and returns true if the end of the reader is reached. -// Careful: the read operation is a side-effect. -func (d deserializationContext) assertEOF() bool { - // Unfortunately we can't always do a zero-byte read here, since some - // reader implementations fail to return EOF. This means assertEOF - _, err := d.readByte() - return err == io.EOF -} - -// newDeserializationContext returns a deserializationContext for a reader -func newDeserializationContext(r io.Reader) *deserializationContext { - // TODO - // bufio is used here to allow debugging via d.dump() - // once this code here is robust enough we can just pass r - return &deserializationContext{bufio.NewReader(r)} -} diff --git a/serialize_test.go b/serialize_test.go deleted file mode 100644 index 496933f..0000000 --- a/serialize_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package opentimestamps - -import ( - "bytes" - "math" - "testing" - - "github.com/stretchr/testify/assert" -) - -func newDeserializationContextFromBytes(in []byte) *deserializationContext { - return newDeserializationContext(bytes.NewBuffer(in)) -} - -func TestReadWrite(t *testing.T) { - magic := []byte("magic") - buf := &bytes.Buffer{} - s := newSerializationContext(buf) - - assert.NoError(t, s.writeBytes([]byte{0x00, 0x01})) - assert.NoError(t, s.writeByte(0x02)) - assert.NoError(t, s.writeBool(true)) - assert.NoError(t, s.writeBool(false)) - assert.NoError(t, s.writeByte(0x03)) - assert.NoError(t, s.writeVarUint(1)) - assert.NoError(t, s.writeBytes([]byte{0x81, 0x00})) - assert.NoError(t, s.writeBytes([]byte{0x81, 0x01})) - assert.NoError(t, s.writeVarUint(0x100)) - assert.NoError(t, s.writeVarUint(uint64(math.MaxUint32)+1)) - assert.NoError(t, s.writeVarUint(math.MaxUint64)) - assert.NoError(t, s.writeBytes([]byte{ - // varunit excess MaxUint64 - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0x01, - })) - assert.NoError(t, s.writeBytes(magic)) - assert.NoError(t, s.writeByte(0)) - assert.NoError(t, s.writeBytes(magic)) - - data := buf.Bytes() - - expectedData := []byte{ - 0x00, 0x01, // bytes [0x00, 0x01] - 0x02, // byte 0x02 - 0xff, // bool true - 0x00, // bool false - 0x03, // bool error - 0x01, // varuint 1 - 0x81, 0x00, // varuint 1 - 0x81, 0x01, // varuint 1 (alternative) - 0x80, 0x02, // varuint 0x100 - - // varunit math.MaxUint32 + 1 - 0x80, 0x80, 0x80, 0x80, 0x10, - - // varunit math.MaxUint64 - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0x01, - - // varunit excess math.MaxUint64 - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0x01, - - // "magic" - 0x6d, 0x61, 0x67, 0x69, 0x63, - // zero - 0x00, - // "magic" - 0x6d, 0x61, 0x67, 0x69, 0x63, - } - - assert.Equal(t, expectedData, data) - - d := newDeserializationContextFromBytes(data) - - { - v, err := d.readBytes(2) - assert.NoError(t, err) - assert.Equal(t, []byte{0x00, 0x01}, v) - } - { - v, err := d.readByte() - assert.NoError(t, err) - assert.Equal(t, byte(0x02), v) - } - { - v, err := d.readBool() - assert.NoError(t, err) - assert.Equal(t, true, v) - } - { - v, err := d.readBool() - assert.NoError(t, err) - assert.Equal(t, false, v) - } - { - _, err := d.readBool() - assert.Error(t, err) - } - { - v, err := d.readVarUint() - assert.NoError(t, err) - assert.Equal(t, uint64(1), v) - } - { - v, err := d.readVarUint() - assert.NoError(t, err) - assert.Equal(t, uint64(1), v) - } - { - v, err := d.readVarUint() - assert.NoError(t, err) - assert.Equal(t, uint64(0x81), v) - } - { - v, err := d.readVarUint() - assert.NoError(t, err) - assert.Equal(t, uint64(0x100), v) - } - { - v, err := d.readVarUint() - assert.NoError(t, err) - assert.Equal(t, uint64(math.MaxUint32)+uint64(1), v) - } - { - v, err := d.readVarUint() - assert.NoError(t, err) - assert.Equal(t, uint64(math.MaxUint64), uint64(v)) - } - { - _, err := d.readVarUint() - assert.Error(t, err) - // read leftover 0x02 - b, err := d.readByte() - assert.NoError(t, err) - assert.Equal(t, byte(0x01), b) - - } - { - assert.NoError(t, d.assertMagic(magic)) - // fails because of in-between 0x00 - assert.Error(t, d.assertMagic(magic)) - } - { - // read leftover byte - _, err := d.readByte() - assert.NoError(t, err) - assert.True(t, d.assertEOF()) - } -} diff --git a/timestamp.go b/timestamp.go deleted file mode 100644 index ec83443..0000000 --- a/timestamp.go +++ /dev/null @@ -1,183 +0,0 @@ -package opentimestamps - -import ( - "bytes" - "fmt" - "io" - "strings" -) - -type dumpConfig struct { - showMessage bool - showFlat bool -} - -var defaultDumpConfig dumpConfig = dumpConfig{ - showMessage: true, - showFlat: false, -} - -// A timestampLink with the opCode being the link edge. The reference -// implementation uses a map, but the implementation is a bit complex. A list -// should work as well. -type tsLink struct { - opCode opCode - timestamp *Timestamp -} - -// A Timestamp can contain many attestations and operations. -type Timestamp struct { - Message []byte - Attestations []Attestation - ops []tsLink -} - -// Walk calls the passed function f for this timestamp and all -// downstream timestamps that are chained via operations. -func (t *Timestamp) Walk(f func(t *Timestamp)) { - f(t) - for _, l := range t.ops { - l.timestamp.Walk(f) - } -} - -func (t *Timestamp) encode(ctx *serializationContext) error { - n := len(t.Attestations) + len(t.ops) - if n == 0 { - return fmt.Errorf("cannot encode empty timestamp") - } - prefixAtt := []byte{0x00} - prefixOp := []byte{} - nextNode := func(prefix []byte) error { - n -= 1 - if n > 0 { - return ctx.writeByte(0xff) - } - if len(prefix) > 0 { - return ctx.writeBytes(prefix) - } - return nil - } - // FIXME attestations should be sorted - for _, att := range t.Attestations { - if err := nextNode(prefixAtt); err != nil { - return err - } - if err := encodeAttestation(ctx, att); err != nil { - return err - } - } - // FIXME ops should be sorted - for _, op := range t.ops { - if err := nextNode(prefixOp); err != nil { - return err - } - if err := op.opCode.encode(ctx); err != nil { - return err - } - if err := op.timestamp.encode(ctx); err != nil { - return err - } - } - return nil -} - -func (t *Timestamp) DumpIndent(w io.Writer, indent int, cfg dumpConfig) { - if cfg.showMessage { - fmt.Fprintf(w, strings.Repeat(" ", indent)) - fmt.Fprintf(w, "message %x\n", t.Message) - } - for _, att := range t.Attestations { - fmt.Fprint(w, strings.Repeat(" ", indent)) - fmt.Fprintln(w, att) - } - - for _, tsLink := range t.ops { - fmt.Fprint(w, strings.Repeat(" ", indent)) - fmt.Fprintln(w, tsLink.opCode) - // fmt.Fprint(w, strings.Repeat(" ", indent)) - // if the timestamp is indeed tree-shaped, show it like that - if !cfg.showFlat || len(t.ops) > 1 { - indent += 1 - } - tsLink.timestamp.DumpIndent(w, indent, cfg) - } -} - -func (t *Timestamp) DumpWithConfig(cfg dumpConfig) string { - b := &bytes.Buffer{} - t.DumpIndent(b, 0, cfg) - return b.String() -} - -func (t *Timestamp) Dump() string { - return t.DumpWithConfig(defaultDumpConfig) -} - -func parseTagOrAttestation(ts *Timestamp, ctx *deserializationContext, tag byte, message []byte, limit int) error { - if tag == 0x00 { - a, err := ParseAttestation(ctx) - if err != nil { - return err - } - ts.Attestations = append(ts.Attestations, a) - } else { - op, err := parseOp(ctx, tag) - if err != nil { - return err - } - newMessage, err := op.apply(message) - if err != nil { - return err - } - nextTs := &Timestamp{Message: newMessage} - err = parse(nextTs, ctx, newMessage, limit-1) - if err != nil { - return err - } - ts.ops = append(ts.ops, tsLink{op, nextTs}) - - } - return nil -} - -func parse(ts *Timestamp, ctx *deserializationContext, message []byte, limit int) error { - if limit == 0 { - return fmt.Errorf("recursion limit") - } - var tag byte - var err error - for { - tag, err = ctx.readByte() - if err != nil { - return err - } - if tag == 0xff { - tag, err = ctx.readByte() - if err != nil { - return err - } - err := parseTagOrAttestation(ts, ctx, tag, message, limit) - if err != nil { - return err - } - } else { - break - } - } - return parseTagOrAttestation(ts, ctx, tag, message, limit) -} - -func newTimestampFromContext(ctx *deserializationContext, message []byte) (*Timestamp, error) { - recursionLimit := 1000 - ts := &Timestamp{Message: message} - err := parse(ts, ctx, message, recursionLimit) - if err != nil { - return nil, err - } - return ts, nil -} - -func NewTimestampFromReader(r io.Reader, message []byte) (*Timestamp, error) { - return newTimestampFromContext(newDeserializationContext(r), message) -} diff --git a/util.go b/util.go deleted file mode 100644 index cd5a1ea..0000000 --- a/util.go +++ /dev/null @@ -1,11 +0,0 @@ -package opentimestamps - -import "encoding/hex" - -func mustDecodeHex(in string) []byte { - out, err := hex.DecodeString(in) - if err != nil { - panic(err) - } - return out -} diff --git a/verifier.go b/verifier.go deleted file mode 100644 index 0a077c0..0000000 --- a/verifier.go +++ /dev/null @@ -1,86 +0,0 @@ -package opentimestamps - -import ( - "fmt" - "math" - "time" - - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" -) - -type Bitcoin interface { - GetBlockHash(height int64) (*chainhash.Hash, error) - GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error) -} - -// VerifyAttestation checks a BitcoinAttestation using a given hash digest. It -// returns the time of the block if the verification succeeds, an error -// otherwise. -func VerifyAttestation(bitcoinInterface Bitcoin, digest []byte, a *BitcoinAttestation) (*time.Time, error) { - if a.Height > math.MaxInt64 { - return nil, fmt.Errorf("illegal block height") - } - blockHash, err := bitcoinInterface.GetBlockHash(int64(a.Height)) - if err != nil { - return nil, err - } - h, err := bitcoinInterface.GetBlockHeader(blockHash) - if err != nil { - return nil, err - } - - merkleRootBytes := h.MerkleRoot[:] - err = a.VerifyAgainstBlockHash(digest, merkleRootBytes) - if err != nil { - return nil, err - } - utc := h.Timestamp.UTC() - - return &utc, nil -} - -// A BitcoinVerification is the result of verifying a BitcoinAttestation -type BitcoinVerification struct { - Timestamp *Timestamp - Attestation *BitcoinAttestation - AttestationTime *time.Time - Error error -} - -// BitcoinVerifications returns the all bitcoin attestation results for the -// timestamp. -func BitcoinVerifications(bitcoinInterface Bitcoin, t *Timestamp) (res []BitcoinVerification) { - t.Walk(func(ts *Timestamp) { - for _, att := range ts.Attestations { - btcAtt, ok := att.(*BitcoinAttestation) - if !ok { - continue - } - attTime, err := VerifyAttestation(bitcoinInterface, ts.Message, btcAtt) - res = append(res, BitcoinVerification{ - Timestamp: ts, - Attestation: btcAtt, - AttestationTime: attTime, - Error: err, - }) - } - }) - return res -} - -// Verify returns the earliest bitcoin-attested time, or nil if none can be -// found or verified successfully. -func Verify(bitcoinInterface Bitcoin, t *Timestamp) (ret *time.Time, err error) { - res := BitcoinVerifications(bitcoinInterface, t) - for _, r := range res { - if r.Error != nil { - err = r.Error - continue - } - if ret == nil || r.AttestationTime.Before(*ret) { - ret = r.AttestationTime - } - } - return -}