Skip to content

Commit

Permalink
Merge branch 'main' into facu/fix-prep-prop-2
Browse files Browse the repository at this point in the history
  • Loading branch information
facundomedica committed Jan 5, 2023
2 parents 2d80482 + 958b4ad commit 641b0a5
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 16 deletions.
16 changes: 15 additions & 1 deletion docs/architecture/adr-050-sign-mode-textual-annex1.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Dec 06, 2021: Initial Draft
* Feb 07, 2022: Draft read and concept-ACKed by the Ledger team.
* Dec 01, 2022: Remove `Object: ` prefix on Any header screen.
* Dec 13, 2022: Sign over bytes hash when bytes length > 32.

## Status

Expand Down Expand Up @@ -254,7 +255,20 @@ Examples:

### bytes

* Bytes are rendered in hexadecimal, all capital letters, without the `0x` prefix.
* Bytes of length shorter or equal to 32 are rendered in hexadecimal, all capital letters, without the `0x` prefix.
* Bytes of length greater than 32 are hashed using SHA256. The rendered text is `SHA-256=`, followed by the 32-byte hash, in hexadecimal, all capital letters, without the `0x` prefix.
* The hexadecimal string is finally separated into groups of 4 digits, with a space `' '` as separator. If the bytes length is odd, the 2 remaining hexadecimal characters are at the end.

Note: Data longer than 32 bytes are not rendered in a way that can be inverted. See ADR-050's [section about invertability](./adr-050-sign-mode-textual.md#invertible-rendering) for a discussion.

#### Examples

Inputs are displayed as byte arrays.

* `[0]`: `00`
* `[0,1,2]`: `0001 02`
* `[0,1,2,..,31]`: `0001 0203 0405 0607 0809 0A0B 0C0D 0E0F 1011 1213 1415 1617 1819 1A1B 1C1D 1E1F`
* `[0,1,2,..,32]`: `SHA-256=5D8F CFEF A9AE EB71 1FB8 ED1E 4B7D 5C8A 9BAF A46E 8E76 E68A A18A DCE5 A10D F6AB`

### address bytes

Expand Down
9 changes: 9 additions & 0 deletions docs/architecture/adr-050-sign-mode-textual.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Sep 07, 2022: Add custom `Msg`-renderers.
* Sep 18, 2022: Structured format instead of lines of text
* Nov 23, 2022: Specify CBOR encoding.
* Dec 14, 2022: Mention exceptions for invertability.

## Status

Expand Down Expand Up @@ -76,6 +77,14 @@ Note that the existence of an inverse function ensures that the
rendered text contains the full information of the original transaction,
not a hash or subset.

We make an exception for invertibility for data which are too large to
meaningfully display, such as byte strings longer than 32 bytes. We may then
selectively render them with a cryptographically-strong hash. In these cases,
it is still computationally infeasible to find a different transaction which
has the same rendering. However, we must ensure that the hash computation is
simple enough to be reliably executed independently, so at least the hash is
itself reasonably verifiable when the raw byte string is not.

### Chain State

The rendering function (and parsing function) may depend on the current chain state.
Expand Down
20 changes: 11 additions & 9 deletions tx/textual/internal/testdata/bytes.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
[
["", ""],
["00", "AA=="],
["66", "Zg=="],
["666F", "Zm8="],
["666F6F", "Zm9v"],
["666F6F62", "Zm9vYg=="],
["666F6F6261", "Zm9vYmE="],
["666F6F626172", "Zm9vYmFy"]
]
["", ""],
["AA==", "00"],
["Zg==", "66"],
["Zm8=", "666F"],
["Zm9v", "666F 6F"],
["Zm9vYg==", "666F 6F62"],
["Zm9vYmE=", "666F 6F62 61"],
["Zm9vYmFy", "666F 6F62 6172"],
["AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", "0001 0203 0405 0607 0809 0A0B 0C0D 0E0F 1011 1213 1415 1617 1819 1A1B 1C1D 1E1F"],
["AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8g", "SHA-256=5D8F CFEF A9AE EB71 1FB8 ED1E 4B7D 5C8A 9BAF A46E 8E76 E68A A18A DCE5 A10D F6AB"]
]
51 changes: 47 additions & 4 deletions tx/textual/valuerenderer/bytes.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package valuerenderer

import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"

"google.golang.org/protobuf/reflect/protoreflect"
)

var (
hashPrefix = "SHA-256="
maxByteLen = 32 // Maximum byte length to be displayed as is. Longer than this, we hash.
)

// NewBytesValueRenderer returns a ValueRenderer for Protobuf bytes, which are
// encoded as capital-letter hexadecimal, without the '0x' prefix.
func NewBytesValueRenderer() ValueRenderer {
Expand All @@ -20,20 +27,56 @@ type bytesValueRenderer struct{}
var _ ValueRenderer = bytesValueRenderer{}

func (vr bytesValueRenderer) Format(ctx context.Context, v protoreflect.Value) ([]Screen, error) {
text := strings.ToUpper(hex.EncodeToString(v.Bytes()))
bz := v.Bytes()

if len(bz) <= maxByteLen {
text := toHex(bz)
return []Screen{{Text: text}}, nil
}

// For long bytes, we display the hash.
hasher := sha256.New()
_, err := hasher.Write(bz)
if err != nil {
return nil, err
}
h := hasher.Sum(nil)

text := fmt.Sprintf("%s%s", hashPrefix, toHex(h))
return []Screen{{Text: text}}, nil
}

func (vr bytesValueRenderer) Parse(_ context.Context, screens []Screen) (protoreflect.Value, error) {
if len(screens) != 1 {
return protoreflect.ValueOfBytes([]byte{}), fmt.Errorf("expected single screen: %v", screens)
return nilValue, fmt.Errorf("expected single screen: %v", screens)
}
formatted := screens[0].Text

data, err := hex.DecodeString(string(formatted))
// If the formatted string starts with `SHA-256=`, then we can't actually
// invert to get the original bytes. In this case, we error.
if strings.HasPrefix(formatted, hashPrefix) {
return nilValue, fmt.Errorf("cannot parse bytes hash")
}

// Remove all spaces between every 4th char, then we can decode hex.
data, err := hex.DecodeString(strings.ReplaceAll(formatted, " ", ""))
if err != nil {
return protoreflect.ValueOfBytes([]byte{}), err
return nilValue, err
}

return protoreflect.ValueOfBytes(data), nil
}

// toHex converts bytes to hex, and inserts a space every 4th character.
func toHex(bz []byte) string {
text := strings.ToUpper(hex.EncodeToString(bz))

var buffer bytes.Buffer
for i, r := range text {
buffer.WriteRune(r)
if i < len(text)-1 && i%4 == 3 {
buffer.WriteRune(' ')
}
}
return buffer.String()
}
10 changes: 8 additions & 2 deletions tx/textual/valuerenderer/bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,23 @@ func TestBytesJsonTestCases(t *testing.T) {

// Round trip
val, err := valrend.Parse(context.Background(), screens)
if err != nil {
// Make sure Parse() only errors because of hashed bytes.
require.Equal(t, "cannot parse bytes hash", err.Error())
require.Greater(t, len(tc.base64), 32)
continue
}
require.NoError(t, err)
require.Equal(t, tc.base64, base64.StdEncoding.EncodeToString(val.Bytes()))
}
}

type bytesTest struct {
hex string
base64 string
hex string
}

func (t *bytesTest) UnmarshalJSON(b []byte) error {
a := []interface{}{&t.hex, &t.base64}
a := []interface{}{&t.base64, &t.hex}
return json.Unmarshal(b, &a)
}

0 comments on commit 641b0a5

Please sign in to comment.